Backend Development 12 min read

Why Spring @Transactional Sometimes Fails: 12 Common Pitfalls and Fixes

This article explains how Spring's declarative transaction management works, why the @Transactional annotation can unexpectedly become ineffective, and presents twelve typical scenarios—such as internal method calls, non‑public methods, final/static modifiers, proxy issues, and incorrect propagation settings—along with practical solutions to ensure reliable transaction handling.

Lobster Programming
Lobster Programming
Lobster Programming
Why Spring @Transactional Sometimes Fails: 12 Common Pitfalls and Fixes

Spring provides both declarative and programmatic transaction management; declarative transactions are implemented via Spring's AOP, which dynamically weaves transaction logic (resource binding, transaction start, commit/rollback, exception conversion) into the appropriate join points of business methods.

With Spring Boot's auto‑configuration, adding @Transactional to a method enables transaction support with a single annotation, but several hidden pitfalls can cause the transaction to be ignored.

1. Internal method call causing @Transactional failure

Spring AOP only works when a method is invoked through a proxy. Calls using this within the same class bypass the proxy, so the transaction is not applied.

<code>@RestController
@RequestMapping("/test")
public class TransactionalController {
    @Resource
    private OrderService orderService;

    @GetMapping("testTransactional")
    public String testTransactional(){
        orderService.findAndInsertOrder();
        return "success";
    }
}
</code>
<code>@Service
public class OrderService {
    @Resource
    private OrderMapper orderMapper;

    @Transactional
    public void findAndInsertOrder(){
        orderMapper.getOrderById(2L);
        Order order = new Order();
        order.setOrderId(123456789L);
        order.setNum(1);
        order.setUserId(123456789L);
        this.insertOrder(order); // internal call – transaction ignored
    }

    @Transactional
    public void insertOrder(Order order){
        orderMapper.insertOrder(order);
        throw new RuntimeException("exception");
    }
}
</code>

Changing the propagation of insertOrder to Propagation.NEVER still shows that the transaction does not roll back because the method is invoked internally.

1.1 Self‑injection

Inject the service into itself so that the internal call goes through the proxy.

<code>@Service
public class OrderService {
    @Resource
    private OrderMapper orderMapper;
    @Resource
    private OrderService orderService; // self‑injection

    @Transactional(propagation = Propagation.NEVER)
    public void insertOrder(Order order){
        orderMapper.insertOrder(order);
    }

    @Transactional
    public void findAndInsertOrder(){
        // ...
        orderService.insertOrder(order); // proxy call – transaction works
    }
}
</code>

1.2 Move the called method to another @Service

Placing the inner method in a separate service forces a proxy call.

<code>@Service
public class OrderService2 {
    @Resource
    private OrderMapper orderMapper;

    @Transactional(propagation = Propagation.NEVER)
    public void insertOrder(Order order){
        orderMapper.insertOrder(order);
    }
}

@Service
public class OrderService {
    @Resource
    private OrderMapper orderMapper;
    @Resource
    private OrderService2 orderService2;

    @Transactional
    public void findAndInsertOrder(){
        // ...
        orderService2.insertOrder(order); // proxy call
    }
}
</code>

1.3 Use AopContext with exposeProxy=true

Enable @EnableAspectJAutoProxy(exposeProxy = true) and obtain the current proxy via AopContext.currentProxy() to invoke the transactional method.

<code>@Transactional
public void findAndInsertOrder(){
    // ...
    ((OrderService) AopContext.currentProxy()).insertOrder(order);
}
</code>
<code>@EnableAspectJAutoProxy(exposeProxy = true)
public class FirstApplication {
    public static void main(String[] args) {
        SpringApplication.run(FirstApplication.class, args);
    }
}
</code>

2. Non‑public method visibility

Spring only creates proxies for public methods. Declaring a transactional method as private prevents the transaction from being applied.

<code>@Transactional
private void findAndInsertOrder(){
    // ...
}
</code>

3. Final or static methods

Methods marked final or static cannot be overridden by proxies, so transactions are ignored.

4. Bean not managed by Spring

The class must be registered as a Spring bean using annotations such as @Component , @Service , @Controller , or @Repository . Otherwise, Spring cannot apply transaction handling.

5. Multithreaded calls

Invoking a transactional method from a new thread breaks the transactional context; the two threads use separate database connections, so rollback in one does not affect the other.

6. Database engine does not support transactions

MyISAM tables in MySQL do not support transactions, while InnoDB does. Ensure the underlying tables use a transactional engine.

<code>SHOW ENGINES;</code>

7. Transaction manager not configured

If no transaction manager bean is defined, Spring cannot start transactions. Spring Boot usually auto‑configures DataSourceTransactionManager .

8. Incorrect propagation settings

Using Propagation.NEVER or other non‑supporting propagation types disables transaction creation and throws an exception if a transaction already exists.

9. Inner method uses a propagation type that disables transactions

When an inner method is annotated with Propagation.NOT_SUPPORTED or similar, it runs outside any transaction, preventing rollback.

10. Swallowing exceptions

If a transactional method catches the exception and does not rethrow it, Spring assumes the operation succeeded and will not roll back.

11. Exception type mismatch

By default, Spring rolls back only on unchecked exceptions ( RuntimeException and Error ). Checked exceptions do not trigger a rollback unless configured.

12. Misconfigured rollbackFor attribute

If rollbackFor references an exception type that is never thrown, the transaction will not roll back even when an error occurs.

By avoiding these pitfalls, developers can ensure that Spring transactions work reliably in their applications.

backendJavaTransactionAOPSpringSpringBoot
Lobster Programming
Written by

Lobster Programming

Sharing insights on technical analysis and exchange, making life better through technology.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.