skip to content

Circular Dependencies & Lazy Init

Spring resolves setter and field cycles with an early reference from its three-level singleton cache, but constructor cycles fail outright unless @Lazy or a redesign breaks them. A frequent question, because that startup failure is one every Spring developer eventually meets.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

6

What is a circular dependency between Spring beans, and how does Spring's handling differ between setter/field injection and constructor injection?

level: juniorimportance: must knowfreq 70%

answer

  1. A→B→A loop
  2. constructor = fails, setter/field = resolvable
  3. instantiate then populate (two phases)
  4. early reference of half-built bean
  5. Boot 2.6 default false

basics

~20 s

A circular dependency is when bean A needs B and B needs A. Spring can resolve this for setter/field injection (it injects a half-built A into B), but constructor injection fails because neither bean can be built first.

solid answer

~40 s

A circular dependency exists when two (or more) beans reference each other, e.g. A depends on B and B depends on A. With setter or field injection Spring can break the cycle: it constructs A (empty), exposes a reference to that half-initialized A, then constructs B and injects that early A reference, finishes B, and finally finishes wiring A. With constructor injection this is impossible — A's constructor needs a finished B and B's constructor needs a finished A, so neither can be instantiated first. Spring detects this and throws BeanCurrentlyInCreationException at startup. Since Spring Boot 2.6 even the resolvable (setter/field) case is rejected by default. The clean fix is to redesign; quick fixes are @Lazy on one injection point or (for setter/field) re-enabling allowCircularReferences.

code

java · 20 lines
java
// Setter/field cycle — historically resolvable
@Component
class A {
    @Autowired B b;   // field injection: set during populate phase
}

@Component
class B {
    @Autowired A a;   // receives the early, half-built A
}

// Constructor cycle — ALWAYS fails: BeanCurrentlyInCreationException
@Component
class X {
    X(Y y) {}         // needs finished Y first
}
@Component
class Y {
    Y(X x) {}         // needs finished X first — deadlock
}

go deeper

for a junior

Know the definition and that setter/field cycles can be resolved but constructor cycles cannot.

for a middle

Explain the instantiate-then-populate phases and why that lets setter/field cycles work.

for a senior

Tie the resolution to early singleton exposure and mention the Boot 2.6 default change.

for a principal

Discuss when a cycle indicates an architectural flaw and how to design it away vs. patch it.

## What is a circular dependency? A **circular (cyclic) dependency** occurs when beans depend on each other directly or transitively, forming a loop: `A → B → A`, or longer chains like `A → B → C → A`. Because Spring must fully create a bean's dependencies before (or during) creating the bean, a cycle raises a chicken-and-egg problem. ## Why injection style matters Spring instantiates a singleton bean in two phases inside `AbstractAutowireCapableBeanFactory`: 1. **Instantiation** — call the constructor to create the raw object. 2. **Population** — inject fields / call setters (`populateBean`) and run init callbacks (`initializeBean`). - **Constructor injection**: the dependency must be supplied *at step 1*. To build `A` you already need a finished `B`, and to build `B` you need a finished `A`. Nothing can be instantiated first, so Spring cannot break the loop. It throws `BeanCurrentlyInCreationException`. - **Setter / field injection**: the dependency is supplied *at step 2*. Spring can finish step 1 for `A` (a bare object exists) and then **expose that half-built `A`** to whoever needs it. So when `B` is being populated and asks for `A`, it receives the early, not-yet-fully-initialized `A` reference. `B` completes, then `A`'s population completes using the finished `B`. ## The mechanism: early singleton exposure Spring uses a **three-level cache** in `DefaultSingletonBeanRegistry` to hand out early references. Right after instantiating `A` (but before populating it), Spring registers an `ObjectFactory` for `A` (`addSingletonFactory`). If something asks for `A` mid-creation, that factory yields an *early reference*. This only works because the reference exists after construction — which is exactly why it fails for constructor cycles (no object exists yet to expose). ## Default behavior changed in Spring Boot 2.6 Historically Spring silently resolved setter/field cycles. Since **Spring Boot 2.6 / Spring Framework 5.3**, `spring.main.allow-circular-references` defaults to **false**, so even resolvable cycles fail fast at startup with a clear error unless you opt back in. The guidance is to treat a cycle as a design smell. ## Fixes - **Redesign** — extract the shared logic into a third bean, or use an event, so the loop disappears (preferred). - **`@Lazy`** on one injection point — injects a lazy proxy, deferring real resolution until first use; works even for constructors. - **Setter/field injection** instead of constructor for one side. - **Re-enable** `spring.main.allow-circular-references=true` (setter/field cycles only) — a band-aid. ## Gotchas - Constructor cycles are **never** auto-resolvable, regardless of the flag. - `@Async`, AOP proxies, or `@Scope`d beans can complicate early exposure (the early reference may need to be a proxy). - The error `BeanCurrentlyInCreationException` and `Requested bean is currently in creation: Is there an unresolvable circular reference?` are the tell-tale messages.

  • Which exception does Spring throw for an unresolvable constructor cycle, and when?
    BeanCurrentlyInCreationException, thrown eagerly at application startup (context refresh) when it tries to instantiate the beans, with a message like 'Is there an unresolvable circular reference?'.
  • Is a circular dependency ever a good design?
    Rarely. It usually signals a missing abstraction or misassigned responsibility. Preferred fixes are extracting a third collaborator, inverting a dependency, or publishing an event rather than relying on Spring to untangle it.

saying these in an interview costs you the question

  • Claiming Spring can resolve constructor-injection cycles
  • Thinking @Autowired on a field and on a constructor behave the same for cycles
  • Believing cycles are still silently allowed by default in modern Spring Boot

context

open as a page

Why do constructor-injection circular dependencies fail while field/setter cycles can be resolved? Explain in terms of Spring's bean creation phases.

level: middleimportance: must knowfreq 65%

basics

~20 s

Spring builds a bean in two steps: construct it, then inject its dependencies. Constructor injection needs the dependency at step one, before any object exists to expose, so the cycle can't be broken. Field/setter injection happens at step two, after a half-built object exists to hand out.

open as a page

What is the allowCircularReferences / spring.main.allow-circular-references setting, what changed in Spring Boot 2.6, and what are its limits?

level: middleimportance: should knowfreq 50%

basics

~10 s

It's a flag controlling whether Spring resolves setter/field circular dependencies. Since Spring Boot 2.6 it defaults to false, so cycles fail fast at startup. Set spring.main.allow-circular-references=true to re-enable. It never fixes constructor cycles.

open as a page

How does @Lazy break a circular dependency, and why does it work even for constructor injection where the three-level cache cannot?

level: seniorimportance: should knowfreq 55%

basics

~20 s

@Lazy on an injection point makes Spring inject a lazy proxy instead of the real bean. The proxy is created immediately with no dependency on the target, so construction succeeds; the real bean is looked up only when a method is first called, by which time both beans exist.

open as a page

Explain Spring's three-level singleton cache and how it resolves a setter/field circular dependency, including what each level holds.

level: seniorimportance: should knowfreq 55%

basics

~20 s

DefaultSingletonBeanRegistry keeps three maps: singletonObjects (fully built beans), earlySingletonObjects (raw early references), and singletonFactories (ObjectFactories that produce an early reference on demand). During a cycle, a dependent gets the early reference via the factory, which is promoted to the early-objects map, breaking the loop.

open as a page

When a bean in a circular dependency is also AOP-proxied, how does Spring ensure the dependent gets the proxy (not the raw target), and when can this throw BeanCurrentlyInCreationException even for a setter/field cycle?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

The level-3 ObjectFactory calls getEarlyBeanReference, which lets AOP post-processors build the proxy early, so a dependent in the cycle receives the proxy. If a different post-processor later wraps the bean into yet another object after the early reference was already exposed, the identities diverge and Spring throws BeanCurrentlyInCreationException.

open as a page