Circular Dependencies in Spring — How to Detect and Fix
Fast recall
Quick revision
Core answer
A needs B to be constructed and B needs A — Spring can't resolve which to build first. With constructor injection, Spring throws BeanCurrentlyInCreationException at startup — fail-fast, clearly names the cycle. With setter or field injection, older Spring versions silently worked (creating one first, injecting a partial reference, then finishing). Spring Boot 2.6+ blocks this by default — you must explicitly enable spring.main.allow-circular-references=true to restore legacy behavior, and the community consensus is: don't. The correct fix is design change, not a framework escape hatch. Three patterns: extract the shared concern into a third class, replace one direction with events (ApplicationEventPublisher), or extract an interface. @Lazy is a last-resort band-aid that hides the real issue.Whether you recognize circular dependencies as a design problem (not a framework bug), know how to fix them properly, and understand the Boot 2.6+ default change
Enabling spring.main.allow-circular-references=true as the fix; using @Lazy as a habit rather than a deliberate last resort; not knowing Boot 2.6 changed the default
Describe the Single Responsibility violation at the root of most cycles; propose extract-shared-concern, events, or interface-extraction as proper fixes; mention Spring's early-singleton-reference trick used internally for setter-injection cycles
Interview-ready guide
Interview answer
A circular dependency means ServiceA needs ServiceB to be created, and ServiceB needs ServiceA — a deadlock. Spring's response depends on the injection style.
Constructor injection
- Spring detects the deadlock immediately and throws
BeanCurrentlyInCreationExceptionat startup — a clear failure message naming all beans in the cycle - Spring Boot 2.6+ extended this to field and setter injection by default — even those cycles now fail at startup unless you opt in with
spring.main.allow-circular-references=true
Why it is a design problem
- Circular dependencies almost always signal that two classes share a responsibility that belongs in a third class
- Or they signal direct coupling where events should decouple the interaction
The three proper fixes
- Extract the shared concern into a class C — both A and B depend on C, not on each other
- Replace one direction with
ApplicationEventPublisher— A publishes an event, B listens; one-way dependency remains, the cycle is gone - Interface segregation — if B only needs one method from A, extract it into a narrow interface; B depends on the interface, not on all of A
@Lazy breaks the cycle by deferring one side but hides the design problem entirely. When someone removes the annotation during refactoring, the BeanCurrentlyInCreationException returns with no explanation of why it was there. Use @Lazy only as a temporary workaround.
Circular Dependencies in Spring — How to Detect and Fix
Direct Answer
A circular dependency is when A needs B to be constructed and B needs A — Spring can't resolve which to build first. With constructor injection, Spring throws BeanCurrentlyInCreationException at startup — fail-fast, clearly names the cycle. With setter or field injection, older Spring versions silently worked (creating one first, injecting a partial reference, then finishing). Spring Boot 2.6+ blocks this by default — you must explicitly enable spring.main.allow-circular-references=true to restore legacy behavior, and the community consensus is: don't. The correct fix is design change, not a framework escape hatch. Three patterns: extract the shared concern into a third class, replace one direction with events (ApplicationEventPublisher), or extract an interface. @Lazy is a last-resort band-aid that hides the real issue.
Common Mistakes & Pitfalls
Enabling spring.main.allow-circular-references=true as the fix; using @Lazy as a habit rather than a deliberate last resort; not knowing Boot 2.6 changed the default
Check your understanding
Answer these without looking back at the article.
- How did Spring resolve setter-injection cycles before Boot 2.6 disabled them by default?
- Why does constructor injection fail at startup while setter injection could succeed?
- What's the difference between using @Lazy and using an interface to break a cycle?
- When is ApplicationEventPublisher the right fix for a would-be circular dependency?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.