Why @Async Breaks @Transactional in Spring 6 and How to Fix It
This article explains why combining Spring's @Async and @Transactional annotations can cause transaction rollback failures, analyzes the proxy mechanisms behind each annotation, reproduces the issue with sample code, and demonstrates how bean ordering affects the outcome.
Problem Description
The author encountered a situation where an asynchronous method annotated with @Async and a transactional method annotated with @Transactional did not roll back the transaction in production, even though the same code rolled back correctly in a local test.
<code>private final JdbcTemplate jdbcTemplate;
public PersonService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
@Async
public void businessOperator() {
System.out.printf("current execute thread: %s%n", Thread.currentThread().getName());
this.jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", 33, "中国🇨🇳");
// simulate exception
System.out.println(1 / 0);
}</code>Running this code locally shows the expected rollback, but the production environment does not.
Proxy Mechanism Analysis
Both @Transactional and @Async are implemented via Spring AOP proxies created by BeanPostProcessor implementations.
Transaction Proxy
Enabling transaction management with @EnableTransactionManagement registers InfrastructureAdvisorAutoProxyCreator , which creates the transaction proxy with the highest precedence.
<code>public abstract class AopConfigUtils {
public static BeanDefinition registerAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}
private static BeanDefinition registerOrEscalateApcAsRequired(
Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
// ...
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
// set highest priority so it outranks other BeanPostProcessors
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
return beanDefinition;
}
}
</code>Async Proxy
Enabling async execution with @EnableAsync registers AsyncAnnotationBeanPostProcessor . Its default order is Ordered.LOWEST_PRECEDENCE , but it can be changed.
<code>public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
// ...
// default order is lowest precedence
bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
return bpp;
}
}
</code>When a bean is already proxied for transactions, the async post‑processor does not create a new proxy; instead it adds its advisor to the existing proxy, usually at the first position. Thus, the async advice runs before the transaction advice.
Reproducing the Issue
To test the impact of advisor ordering, the author set the async configuration to the highest precedence:
<code>@EnableAsync(order = Ordered.HIGHEST_PRECEDENCE)
public class ECSApplication {
// ...
}
</code>Even with matching precedence, the transaction still failed to roll back in the production scenario, confirming that ordering alone does not resolve the problem.
Conclusion
The root cause lies in the interaction between the two proxy creators: InfrastructureAdvisorAutoProxyCreator (transaction) has higher priority than AsyncAnnotationBeanPostProcessor (async). Because the async advisor is added first, the async task executes before the transaction, leading to a situation where the exception occurs outside the transactional context in production.
Understanding the proxy creation order and the beforeExistingAdvisors flag is essential when combining @Async with @Transactional in Spring applications.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.