You start your Spring Boot application, expecting a smooth startup, but instead, the console explodes with a BeanCurrentlyInCreationException. This error is the technical manifestation of the "chicken and the egg" problem. It occurs when Service A requires Service B to be initialized, but Service B simultaneously requires Service A. Spring finds itself in an infinite loop, unable to complete the instantiation of either component.
While Spring Boot 2.6 and later versions disable circular references by default to encourage better design, many legacy projects or complex systems still encounter this bottleneck. In this guide, you will learn how to identify the root cause of cyclic dependencies and apply both immediate workarounds and long-term architectural fixes to keep your dependency graph clean and maintainable.
TL;DR — To quickly resolve a BeanCurrentlyInCreationException, add the @Lazy annotation to one of the interdependent constructor arguments. For a permanent fix, refactor your code by extracting the shared logic into a third service or using event-based communication to break the direct coupling.
Understanding the Symptoms
The error typically appears during the ApplicationContext refresh phase. You will see a stack trace that points directly to the beans involved in the loop. The most critical part of the message looks like this:
Error creating bean with name 'serviceA': Requested bean is currently in creation:
Is there an unresolvable circular reference?
Spring is explicit about which beans are causing the trouble. If you examine the logs further down, you will see a chain of bean names. For example: serviceA -> serviceB -> serviceA. This indicates a direct cycle. In more complex scenarios, the chain might involve five or six different beans before returning to the start, making it harder to track without proper log analysis.
When I investigated this issue in a Spring Boot 3.x project recently, the logs indicated that a security configuration bean was waiting on a user details service, which in turn required the security configuration to password-encode a default user. This type of hidden recursion is common in security and logging modules where cross-cutting concerns overlap with core business logic.
Common Causes of Cyclic Dependencies
1. Direct Constructor Injection
This is the most frequent cause. Constructor injection is the recommended way to handle dependencies because it ensures immutability and makes testing easier. However, Spring cannot instantiate a class if its constructor requires an object that hasn't been built yet. If Class A(B b) and Class B(A a) both exist, Spring has no "entry point" to start the instantiation process.
2. Circular @Bean Definitions
If you define your beans manually in a @Configuration class, you might accidentally create a loop. For instance, method beanA() calls beanB(), and beanB() calls beanA(). Because these methods are intercepted by CGLIB to ensure singleton behavior, the circular call triggers the exception immediately during the context startup.
3. Self-Injection for AOP Proxies
Sometimes developers inject a service into itself to trigger asynchronous methods (@Async) or transactional boundaries (@Transactional). While this works in some configurations, it often leads to a BeanCurrentlyInCreationException if the proxying mechanism cannot resolve the bean state fast enough during the initial construction phase.
Modern Spring versions have tightened the rules around these patterns. While older versions of Spring (pre-Boot 2.6) tried to resolve these automatically using internal caches, the current default behavior is to fail fast. This is a design choice to prevent unstable runtime behavior and unpredictable bean states.
How to Fix BeanCurrentlyInCreationException
Solution 1: Using the @Lazy Annotation
The fastest way to break the cycle is the @Lazy annotation. When you mark a dependency as lazy, Spring does not fully initialize the bean at startup. Instead, it creates a "lazy-resolution proxy." The actual bean is only created and injected the first time a method on that proxy is called.
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
By using @Lazy, you tell Spring: "Don't worry about Service B right now; just give me a placeholder." This allows ServiceA to finish its construction, which then allows ServiceB to be constructed normally later.
Solution 2: Switching to Setter Injection
While constructor injection is preferred, setter injection allows Spring to create the bean instance first and inject the dependencies later. This breaks the "locked constructor" problem.
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Lazy everywhere can hide architectural flaws. Use these as temporary patches while you plan a proper refactoring of your service layers.
Solution 3: Architectural Refactoring (The Clean Fix)
If two services depend on each other, they often share a specific responsibility that belongs in a third class. Consider extracting the common logic. For example, if OrderService and NotificationService are circular, perhaps they both need a UserPreferencesProvider. By moving that logic to a new class, both services can depend on the provider without needing each other.
// Instead of A -> B and B -> A
// Use A -> C, B -> C
@Service
public class SharedLogicService {
// Methods used by both Service A and Service B
}
Verifying the Solution
Once you have applied a fix, you must verify that the application starts and that the beans are correctly wired. You can check this by creating a simple integration test using @SpringBootTest. This test ensures the ApplicationContext loads without throwing exceptions.
@SpringBootTest
class ApplicationContextTest {
@Test
void contextLoads() {
// If this test passes, the cyclic dependency is resolved
}
}
Beyond passing the startup check, verify that the bean behavior is correct. When using @Lazy, there is a small risk of a NullPointerException if the bean is accessed in a way that bypasses the proxy, or if there's a threading issue during the first call. Perform a manual check of the features involving these services to ensure the logic remains intact.
Check your console output for any warnings. Even if the application starts, Spring may log details about bean initialization order that can give you clues about potential performance bottlenecks caused by lazy loading or proxy overhead.
Preventing Future Dependency Loops
The best way to handle BeanCurrentlyInCreationException is to prevent it through better design patterns. High-quality software follows the Dependency Inversion Principle and the Single Responsibility Principle.
- Keep Services Small: Massive services with dozens of methods are more likely to create dependency loops. Break them down into smaller, focused components.
- Use Events: Instead of Service A calling Service B directly, have Service A publish a
CustomEvent. Service B can then listen for that event using@EventListener. This completely decouples the two classes at the DI level. - Interface-Based Injection: Injecting interfaces rather than concrete implementations can sometimes help Spring's proxying mechanism resolve dependencies more flexibly.
- Monitor with Tools: Use tools like IntelliJ IDEA's "Dependency Structure Matrix" or SonarQube to visualize your project's dependency graph and spot cycles before they reach the compiler.
- Identify the cycle using Spring's startup logs.
- Use
@Lazyfor an immediate, non-intrusive fix. - Prefer architectural refactoring to remove the cycle entirely.
- Avoid setting
allow-circular-references=trueinapplication.propertiesas it is a global setting that can hide deeper bugs.
Frequently Asked Questions
Q. Why did my application start before but fails after updating to Spring Boot 2.6+?
A. Starting with Spring Boot 2.6, circular references are disabled by default. Previously, Spring would attempt to resolve them automatically, which often led to subtle bugs. You can re-enable it using spring.main.allow-circular-references=true, but it is highly recommended to fix the underlying design instead.
Q. Does @Lazy affect the performance of my Spring Boot application?
A. The impact is negligible for most applications. There is a very tiny overhead when the bean is first accessed as Spring must instantiate the real object and swap the proxy. However, it can slightly improve startup time since the bean creation is deferred until actually needed.
Q. Can I have a circular dependency between two @Configuration classes?
A. Yes, and these are often harder to fix. If ConfigA uses @Import(ConfigB.class) and ConfigB uses @Import(ConfigA.class), you will hit a similar exception. The fix is to move shared bean definitions to a third CommonConfig class that both can safely import.
By understanding the mechanics of how Spring manages its bean lifecycle, you can transform a frustrating BeanCurrentlyInCreationException into an opportunity to improve your application's architecture. Focus on clear boundaries and unidirectional data flow to ensure your Spring Boot projects remain scalable and error-free.
Post a Comment