What are the key edge cases and gotchas of @DependsOn: circular depends-on, interaction with @Lazy, and exactly what 'initialized before' guarantees?
answer
- cycle → BeanCreationException (circular depends-on)
- @DependsOn forces a @Lazy target eager
- guarantees FULL init incl. @PostConstruct
- proxy created in postProcessAfterInitialization
- bad name → NoSuchBeanDefinitionException
basics
~10 sA depends-on cycle fails startup with a BeanCreationException. @DependsOn forces the target to be created even if it's @Lazy, defeating laziness. 'Initialized before' means fully created including @PostConstruct/afterPropertiesSet, not just constructed.
solid answer
~50 sThree gotchas. (1) Circular @DependsOn — A depends-on B and B depends-on A — is not resolvable like a constructor cycle sometimes is; the container throws a BeanCreationException reporting a circular depends-on relationship at startup. (2) @DependsOn and @Lazy interact: naming a @Lazy bean in @DependsOn forces it to be initialized eagerly when the dependent bean is created, so it effectively overrides laziness for that path. Conversely, marking the dependent bean @Lazy means the whole chain (including the depended-on beans) is only initialized when the dependent is first requested. (3) The guarantee is full initialization: by the time the annotated bean's constructor runs, each depended-on bean has completed construction, dependency injection, and all init callbacks (@PostConstruct, InitializingBean.afterPropertiesSet, custom init-method). It's not a mere 'object allocated' guarantee — the side effect you're ordering for has actually happened.
code
java · 13 lines// (1) Circular depends-on -> startup BeanCreationException
@Component("a") @DependsOn("b") class A {}
@Component("b") @DependsOn("a") class B {} // "Circular depends-on relationship"
// (2) @DependsOn forces a @Lazy bean to initialize eagerly on this path
@Lazy @Component("cacheSeeder")
class CacheSeeder { @PostConstruct void seed() { /* runs when Consumer is built */ } }
@Component // eager
@DependsOn("cacheSeeder") // -> cacheSeeder is initialized now, laziness overridden
class Consumer { /* seed() has already run before this ctor */ }
// (3) 'initialized before' = full lifecycle incl. @PostConstruct completedgo deeper
Know a bad name fails startup and that it guarantees the target is ready first.
Know cycles fail and that the guarantee includes @PostConstruct completion.
Explain the @Lazy interaction in both directions and the full-lifecycle guarantee precisely.
Reason about proxy creation timing (postProcessAfterInitialization) and how eager-forcing a @Lazy bean can defeat intended startup/memory optimizations.
## 1. Circular depends-on If `A @DependsOn("B")` and `B @DependsOn("A")`, there is no valid initialization order. Unlike a *field/setter* injection cycle among singletons — which Spring can sometimes break with early bean references / proxies — a `@DependsOn` cycle is a hard ordering contradiction. Startup fails with a `BeanCreationException` whose message reports a **circular depends-on relationship** between the beans. Transitive cycles (A→B→C→A) fail the same way. Fix by breaking the cycle: at least one of the beans must not require the other to precede it. (Note: constructor-injection cycles among singletons also fail, but for a different reason — unresolvable construction — whereas `@DependsOn` fails specifically on the ordering constraint.) ## 2. Interaction with @Lazy `@Lazy` on a singleton defers its creation until first requested rather than at context startup. `@DependsOn` and `@Lazy` interact in two directions: - **Depended-on bean is `@Lazy`:** When the *dependent* bean is being created, Spring must satisfy its `@DependsOn`, so it **forces the lazy bean to initialize now**. The `@DependsOn` wins for that path — the lazy bean is created (at latest) when the dependent bean is. Laziness is effectively overridden. - **Dependent bean is `@Lazy`:** The dependent bean isn't created at startup, so its `@DependsOn` targets aren't forced at startup either. When the dependent is finally requested, Spring first initializes the depends-on targets, then the dependent. So the whole ordered chain is deferred together. A subtle consequence: putting `@DependsOn` on an eager bean pointing at a `@Lazy` bean silently makes the lazy bean eager, which may surprise someone who added `@Lazy` for startup-time or memory reasons. ## 3. What 'initialized before' actually guarantees The guarantee is **full initialization**, not mere allocation. Before the annotated bean's own construction begins, each named depended-on bean has gone through the entire creation lifecycle: 1. Instantiation (constructor). 2. Property/dependency population (injection). 3. `BeanPostProcessor` `postProcessBeforeInitialization`. 4. Init callbacks: `@PostConstruct` → `InitializingBean.afterPropertiesSet()` → custom `init-method`/`@Bean(initMethod=...)`. 5. `BeanPostProcessor` `postProcessAfterInitialization` (this is where AOP proxies are created). So the *side effect* you used `@DependsOn` for — the `@PostConstruct` that seeds a cache or registers a driver — is guaranteed complete. This is the whole point: it wouldn't be useful if it only guaranteed the object existed but its init hadn't run. ## Other gotchas - **Unknown name → startup failure.** A name that resolves to no bean definition → `NoSuchBeanDefinitionException`. - **It doesn't compose with `@Order`.** People sometimes reach for `@DependsOn` to order a collection; that's the wrong tool — `@DependsOn` orders *instantiation*, not list position. - **Multiple names:** `@DependsOn({"a","b"})` forces both; their relative order among themselves is not specified by this annotation. - **Not transitive by declaration:** if A depends-on B and B depends-on C, C still precedes B precedes A because each edge is honored — but that's the graph doing it, not a special transitivity feature; still, you don't need to redeclare C on A.
- If you mark the depended-on bean @Lazy but reference it in another bean's @DependsOn, does it stay lazy?No, not on that path. Satisfying @DependsOn requires the target to be initialized before the dependent bean is created, so Spring eagerly initializes the lazy bean at that point. The @DependsOn requirement overrides the laziness for the dependent's creation.
- By the time the annotated bean's constructor runs, has the depended-on bean's @PostConstruct executed?Yes. @DependsOn guarantees full initialization of the target — constructor, injection, and all init callbacks including @PostConstruct and afterPropertiesSet — complete before the dependent bean begins construction. That's what makes it usable for ordering side effects.
saying these in an interview costs you the question
- Thinking a @DependsOn cycle is resolved automatically like some injection cycles.
- Assuming @Lazy always wins over @DependsOn (it doesn't — @DependsOn forces eager init).
- Believing 'before' only means 'object allocated', not that init callbacks ran.