skip to content

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%

answer

  1. spring.main.allow-circular-references
  2. default flipped to false in Boot 2.6
  3. setter/field only — never constructor
  4. global switch vs @Lazy surgical
  5. fail-fast: design smell surfaced

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.

solid answer

~40 s

allowCircularReferences is a boolean on AbstractAutowireCapableBeanFactory (exposed in Boot as spring.main.allow-circular-references) that governs whether Spring will use early singleton exposure to resolve setter/field circular dependencies. Before Spring Boot 2.6 / Spring 5.3 it was effectively on, so resolvable cycles were silently untangled. From Boot 2.6 it defaults to false: any circular reference, even a resolvable field/setter one, fails at startup with BeanCurrentlyInCreationException and a message pointing you to the cycle. The rationale was that cycles are a design smell better surfaced early. Limits: enabling it only helps setter/field cycles — constructor-injection cycles are structurally unresolvable and still fail regardless of the flag. In Boot you can set it via properties or ApplicationContext customization; the preferred path is to fix the design or use @Lazy rather than flip the flag globally.

code

java · 11 lines
java
// application.yml
// spring:
//   main:
//     allow-circular-references: true   # re-enable setter/field cycle resolution

// Or programmatically:
public static void main(String[] args) {
    SpringApplication app = new SpringApplication(App.class);
    app.setAllowCircularReferences(true); // does NOT help constructor cycles
    app.run(args);
}

go deeper

for a junior

Know the property name and that Boot 2.6 made cycles fail by default.

for a middle

Explain what it controls (setter/field early exposure) and its constructor-cycle limit.

for a senior

Give the migration guidance: fix vs @Lazy vs flag, and why fail-fast is preferred.

for a principal

Weigh global flag vs surgical @Lazy vs architectural redesign across a large codebase migration.

## What the flag controls `AbstractAutowireCapableBeanFactory.setAllowCircularReferences(boolean)` toggles whether the container will attempt **early singleton exposure** (the three-level-cache trick) to resolve setter/field cycles. When `false`, Spring refuses to hand out an early reference during a cycle and instead throws. In Spring Boot it's surfaced as the property **`spring.main.allow-circular-references`** (mapped onto `SpringApplication.setAllowCircularReferences`). ## The Spring Boot 2.6 change - **Before Boot 2.6 (Spring 5.3.x era):** the flag was effectively enabled, so field/setter cycles were resolved silently — many apps had cycles without knowing. - **From Boot 2.6:** the default flipped to **false**. Now *any* circular reference — even a resolvable one — fails during context refresh with `BeanCurrentlyInCreationException` and a helpful message listing the cycle and suggesting fixes. - **Motivation:** circular dependencies are usually a design smell; failing fast forces you to notice and address them rather than shipping a fragile, order-sensitive graph. ## How to re-enable (if you must) 1. Property: `spring.main.allow-circular-references=true` in `application.properties`/`application.yml`. 2. Programmatically: `new SpringApplicationBuilder(App.class).allowCircularReferences(true)...` or `app.setAllowCircularReferences(true)`. 3. Bean-factory customizer: `BeanFactoryPostProcessor` calling `setAllowCircularReferences(true)`. ## Hard limits - **Constructor cycles are NOT fixed by this flag.** They are structurally unresolvable (no object to expose early). Setting it `true` still yields `BeanCurrentlyInCreationException` for a pure constructor cycle. - **Prototype cycles** are never resolvable either — no shared singleton to cache. - It's a **global** switch (whole application), whereas `@Lazy` is a **surgical** fix at one injection point. Prefer the surgical fix or a redesign. ## Decision guidance 1. **Best:** remove the cycle — extract a third collaborator, invert a dependency, or publish an `ApplicationEvent`. 2. **Targeted patch:** `@Lazy` on one injection point (works for all injection styles, no global flag change). 3. **Last resort:** flip `allow-circular-references=true` — only masks resolvable setter/field cycles and leaves the smell in place. ## Gotcha Teams upgrading to Boot 2.6+ sometimes hit sudden startup failures for cycles that previously 'worked'. The proper response is to fix the cycle, not blanket-enable the flag, though enabling it is a valid temporary unblock during migration.

  • You upgraded to Boot 2.6 and startup now fails with a circular-reference error. What's the correct response?
    Diagnose and remove the cycle — extract a third bean, invert a dependency, or use @Lazy on one injection point. Flipping spring.main.allow-circular-references=true is only a temporary migration unblock, not a fix, and won't help if the cycle is constructor-based.
  • Does allow-circular-references=true resolve a constructor-injection cycle?
    No. Constructor cycles are structurally unresolvable regardless of the flag; you still get BeanCurrentlyInCreationException. Only @Lazy or a redesign works there.

saying these in an interview costs you the question

  • Believing the flag fixes constructor cycles
  • Thinking cycles are still resolved by default in modern Boot
  • Treating flipping the flag as the recommended fix rather than a stopgap

context