Why @Async Annotation Fails with Circular Dependencies in Spring and How to Resolve It
This article explains how Spring's @Async annotation interacts with circular dependencies, why it triggers BeanCurrentlyInCreationException, and provides multiple strategies—including adjusting dependencies, using @Lazy, or tweaking bean factory settings—to solve the problem.
A colleague encountered a BeanCurrentlyInCreationException when a Spring project containing circular dependencies was started, and the issue disappeared after removing the @Async annotation from a service method.
The exception stack trace is shown below:
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [BService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over‑eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)The root cause is a circular reference between AService and BService , where AService.save() is annotated with @Async :
@Component
public class AService {
@Resource
private BService bService;
@Async
public void save() {
// ...
}
}
@Component
public class BService {
@Resource
private AService aService;
}Spring resolves most circular dependencies by exposing an early bean reference, but @Async is processed by AsyncAnnotationBeanPostProcessor , which creates a proxy only after the bean’s initialization phase. The processor is activated by @EnableAsync .
The post‑processor works through postProcessAfterInitialization :
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (this.advisor == null || bean instanceof AopInfrastructureBean) {
return bean;
}
if (bean instanceof Advised) {
// add advisor to existing proxy
}
if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
proxyFactory.addAdvisor(this.advisor);
return proxyFactory.getProxy(getProxyClassLoader());
}
return bean;
}Spring’s general AOP support is provided by AnnotationAwareAspectJAutoProxyCreator , which also implements BeanPostProcessor and creates proxies during the early‑reference phase via SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference :
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
this.earlyProxyReferences.add(cacheKey);
}
return wrapIfNecessary(bean, beanName, cacheKey);
}Because AsyncAnnotationBeanPostProcessor does **not** implement SmartInstantiationAwareBeanPostProcessor , it does not participate in the early‑reference step. When the circular dependency is resolved, the early reference of AService is the raw bean (or a proxy created by the AOP processor), but later AsyncAnnotationBeanPostProcessor wraps the bean again to apply @Async . The final bean instance therefore differs from the early reference, causing Spring to throw BeanCurrentlyInCreationException as shown in the source code:
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// throw BeanCurrentlyInCreationException
}
}
}To solve the problem, several approaches are possible:
Refactor the design to eliminate the circular dependency.
Remove @Async and use a manual thread pool for asynchronous execution.
Mark one side of the injection with @Lazy so the dependency is resolved lazily:
@Component
public class AService {
@Resource
@Lazy
private BService bService;
@Async
public void save() { }
}Set allowRawInjectionDespiteWrapping to true via a custom BeanFactoryPostProcessor :
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
}
}While the last option removes the exception, it is not recommended because it may leave the bean without the intended proxy, breaking the @Async behavior.
In summary, @Async fails with circular dependencies because its proxy creation occurs after early bean exposure, resulting in two different instances for the same bean. Adjusting the dependency graph, using @Lazy , or configuring the bean factory are the recommended ways to avoid the issue.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.