How does Spring handle circular dependencies between beans?
Quick Answer
For singleton beans using setter/field injection, Spring can resolve circular dependencies (A needs B, B needs A) via a three-level cache that exposes early, not-yet-fully-initialized bean references to break the cycle. For constructor injection, this trick isn't possible (both constructors need a fully-built argument before either can complete), so a circular dependency there fails at startup with a BeanCurrentlyInCreationException — which is usually a sign the design itself should be reworked rather than the injection style changed.
Detailed Answer
A circular dependency occurs when Bean A depends on Bean B, and Bean B (directly or transitively) depends back on Bean A.
With setter/field injection, Spring can resolve this for singleton beans using a mechanism internally called the "three-level cache" of early bean references: while constructing A, Spring notices A needs B; while constructing B, Spring notices B needs A — but instead of recursing infinitely, Spring exposes an early reference to the partially-constructed A (created but not yet fully populated/initialized) to satisfy B's dependency, then finishes populating both. This works because with setter/field injection, an object can exist (via its no-arg-friendly construction path) before all its dependencies are actually set.
@Component
class A { @Autowired B b; }
@Component
class B { @Autowired A a; } // circular, but resolvable via setter/field injection
With constructor injection, this trick is impossible: A's constructor needs a fully-built B as an argument, and B's constructor needs a fully-built A — neither object can exist yet to serve as an "early reference," since construction itself is blocked on the other. Spring detects this and fails fast at startup with BeanCurrentlyInCreationException rather than silently falling back to some other injection style.
@Component
class A { A(B b) {} } // constructor injection
@Component
class B { B(A a) {} } // circular — fails at startup, cannot be resolved
Practical guidance: rather than switching back to setter injection just to "fix" a circular dependency, treat it as a design smell — it usually indicates two classes are too tightly coupled and should be refactored (e.g., extracting shared logic into a third bean both depend on, or using @Lazy on one side as a genuinely last-resort workaround if a refactor isn't immediately feasible). Constructor injection surfacing this problem loudly at startup, rather than Spring quietly working around it, is generally considered a feature, not a limitation.