Circular Dependency in Spring and How to Fix It
Fast recall
Quick revision
Core answer
Whether you recognise a circular dependency as a design smell and reach for refactoring rather than a quick annotation patch.
Believing @Lazy or field injection 'fixes' the cycle — they let it compile but leave the bad design and its risks in place.
Explain that the cleanest fix extracts shared logic into a third bean, and that setting allow-circular-references is a temporary bandage.
Interview-ready guide
Interview answer
A cycle usually means two services own overlapping work. For example, OrderService calls PaymentService and PaymentService calls OrderService. Constructor injection fails because there is no valid creation order. Older Spring behavior could resolve some setter or field cycles with early references, but Spring Boot's default rejects circular references and proxying can make early references unsafe. First split orchestration into a third component or use a domain event for the callback. @Lazy or ObjectProvider can break construction timing when the relationship is genuinely indirect, but they do not remove the logical cycle.
Circular Dependency in Spring and How to Fix It
Direct Answer
A circular dependency exists when bean A requires bean B while bean B also requires A. Constructor cycles cannot be created because neither object can be completed first. Modern Spring Boot also disallows circular references by default, including cycles that older setter or field injection could sometimes resolve. The durable fix is to redesign responsibilities; @Lazy may defer one edge but should not be the default solution.
Code Example
@Service
class OrderService {
private final PaymentService paymentService;
// @Lazy injects a proxy and defers real creation, breaking the cycle
OrderService(@Lazy PaymentService paymentService) {
this.paymentService = paymentService;
}
}
This compiles and runs, but the tight coupling remains — prefer refactoring when you can.
Common Mistakes & Pitfalls
- Using @Lazy as the primary solution: it just defers bean creation and hides the flaw. The circular relationship still exists and still hurts testing and maintenance.
- Switching to field injection: changing to
@Autowiredfields makes the cycle 'work' but trades away immutability, testability, and clarity — the code gets worse, not better. - Not asking why the cycle exists: accepting it as inevitable misses the real fix. Ask why these classes need each other, then extract shared logic or split responsibilities.
Check your understanding
Answer these without looking back at the article.
- How does @Lazy resolve circular dependencies?
- What does BeanCurrentlyInCreationException mean?
- Which Spring Boot property allows circular references?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.