Backend Development 9 min read

Why @Transactional Fails with @Async in Spring Boot 3 – A Deep Dive

This article explains how @Transactional and @Async normally cooperate in Spring Boot, demonstrates cases where the transaction does not roll back when @EnableAsync order is changed, analyzes the underlying AOP proxy creation and bean post‑processor execution order, and introduces new Spring 6.2 transaction‑rollback configuration options.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Why @Transactional Fails with @Async in Spring Boot 3 – A Deep Dive

1. Introduction

In Spring Boot development, the @Transactional and @Async annotations are frequently used for transaction management and asynchronous processing, respectively.

@Transactional declares that a method should run within a transactional context, guaranteeing ACID properties via AOP proxies (default propagation PROPAGATION_REQUIRED ).

@Async marks a method for asynchronous execution, delegating the call to a thread pool to avoid blocking the main thread.

2. Problem Reproduction

2.1 Code Example

<code>@Transactional
@Async
public void processProduct(Product product) {
    this.productRepository.saveAndFlush(product);
    this.emailService.send();
}

@Service
public class EmailService {
    public void send() {
        System.err.printf("%s - 发送邮件", Thread.currentThread().getName());
        System.err.println(1 / 0); // deliberate exception
    }
}
</code>

The send method throws an exception. The question is whether the transaction in processProduct will be rolled back.

2.2 Unit Test

<code>@Resource
private ProductService productService;

@Test
public void testCreateProduct() {
    Product product = new Product("Spring全家桶实战案例源码", 70D);
    this.productService.processProduct(product);
}
</code>

Running the test with the default configuration shows that no data is inserted into the database, confirming that the transaction is rolled back as expected.

2.3 Changing Execution Order

<code>@Configuration
@EnableAsync(order = Ordered.HIGHEST_PRECEDENCE)
public class AsyncConfig {
}
</code>

After setting @EnableAsync to the highest precedence and re‑running the test, the database records are persisted, indicating that the transaction did not roll back.

3. Root Cause Analysis

3.1 Proxy Creation Mechanism

When spring-boot-starter-aop is on the classpath, Spring registers AnnotationAwareAspectJAutoProxyCreator via @EnableAspectJAutoProxy . This BeanPostProcessor creates proxies based on Advisors.

High‑level aspects declared with @Aspect are turned into low‑level Advisor objects.

Custom Advisor implementations can also be provided directly.

The proxy is built by combining the BeanPostProcessor with the appropriate Advisor.

3.2 @Transactional Implementation

Including spring-boot-starter-data-jpa or spring-boot-starter-data-jdbc triggers @EnableTransactionManagement , which registers BeanFactoryTransactionAttributeSourceAdvisor and InfrastructureAdvisorAutoProxyCreator . However, AnnotationAwareAspectJAutoProxyCreator has higher priority, so it ultimately creates the transactional proxy.

3.3 @Async Implementation

@EnableAsync registers AsyncAnnotationBeanPostProcessor and its advisor AsyncAnnotationAdvisor . If the target bean is already proxied, the async advisor is simply added to the existing proxy.

3.4 BeanPostProcessor Execution Order

By default, AnnotationAwareAspectJAutoProxyCreator has the highest precedence, so it creates the @Transactional proxy first, followed by the @Async proxy. The async thread runs inside the transaction, allowing rollback on exceptions.

When @EnableAsync(order = Ordered.HIGHEST_PRECEDENCE) is set, the async processor runs before the transactional one. The async proxy is created first; then the transactional processor creates a second proxy that wraps the async proxy. The transaction starts on the main thread, but the async method executes in a separate thread, so an exception in the async task does not trigger a rollback.

4. New Feature in Spring 6.2

Starting with Spring 6.2, @EnableTransactionManagement adds a rollbackOn attribute, allowing global configuration of rollback policies without annotating each @Transactional method.

<code>@Configuration
@EnableTransactionManagement(rollbackOn = RollbackOn.ALL_EXCEPTIONS)
public class TxConfig {
}
public enum RollbackOn {
    RUNTIME_EXCEPTIONS,
    ALL_EXCEPTIONS
}
</code>

This simplifies handling of exception‑driven rollbacks across the application.

5. Conclusion

Under default settings, @Transactional and @Async work together seamlessly. Adjusting the execution order of @EnableAsync can break this coordination, causing transactions not to roll back when async tasks fail. The new rollbackOn attribute in Spring 6.2 provides a more convenient way to control rollback behavior globally.

AOPSpring Bootasynchronous processingAsyncTransactionalTransaction Management
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.