When should you use @DependsOn instead of expressing the dependency through normal injection, and why is it usually considered a last resort?
answer
- injection first, @DependsOn last resort
- side-effect dependency, no reference
- String name → not refactor-safe
- hides coupling on global/static state
- refactor side effect into injectable collaborator
basics
~20 sUse @DependsOn only when a bean must exist before another for a side effect but there is no reference to inject. Prefer injection because it's type-safe, self-documenting, and refactor-proof; @DependsOn relies on fragile string names.
solid answer
~50 sThe default and best way to order beans is injection: if A needs B, inject B — Spring infers ordering from the graph and you get a compile-checked, self-documenting reference. @DependsOn is the escape hatch for when the ordering requirement is a *side effect* with no reference: bean B's @PostConstruct mutates global/static state, registers a driver, seeds a cache, or runs a migration, and A depends on that state, not on B itself. Since there's nothing to inject, you name B in @DependsOn. It's a last resort because: (1) it uses String bean names, so a rename silently breaks it at runtime, not compile time; (2) it hides real coupling — the true dependency is on hidden global state, which is itself a smell; (3) it doesn't give A access to B. Where possible, refactor the side effect into an explicit collaborator and inject it instead.
code
java · 21 lines// SMELL: ordering via @DependsOn on hidden static state
@Component("featureFlagLoader")
class FeatureFlagLoader {
@PostConstruct void load() { GlobalFlags.populate(); } // static side effect
}
@Component
@DependsOn("featureFlagLoader")
class Router { Router() { if (GlobalFlags.enabled("x")) {/*...*/} } }
// BETTER: make the capability an injectable collaborator — ordering is now implicit
@Component
class FeatureFlags { // holds the state as a real bean
private final Map<String,Boolean> flags = load();
boolean enabled(String k){ return flags.getOrDefault(k,false); }
}
@Component
class Router2 {
Router2(FeatureFlags flags) { // typed reference -> Spring orders it, no @DependsOn
if (flags.enabled("x")) {/*...*/}
}
}go deeper
Know injection is preferred and @DependsOn is for side-effect ordering.
Give a concrete legitimate use case and one downside (string names).
Articulate the fragility (string names, hidden coupling) and the refactor to an injectable collaborator.
Frame it as overriding the container's inferred graph; weigh legacy/JVM-global cases where the refactor is infeasible.
## Two ways to order beans **1. Injection (preferred).** When bean A holds a reference to bean B — constructor parameter, `@Autowired` field/setter — Spring's dependency resolver adds an edge A→B and guarantees B is created first. The ordering is a *byproduct* of a real, type-checked reference. **2. `@DependsOn` (last resort).** A pure ordering directive by String name, with no reference created. Use it *only* when there is genuinely nothing to inject. ## The legitimate use case: side-effect dependencies `@DependsOn` earns its place when one bean's *initialization side effect* — not its API — is what another bean needs: - **Global/JVM state:** registering a JDBC `Driver`, installing a `Security` provider, setting a system property, initializing a native library. - **Static caches / registries:** a bean whose `@PostConstruct` populates a static registry that another bean reads at startup. - **Data/schema seeding:** a manual data-loader or migration bean that must run before a bean that queries that data (though Flyway/Liquibase auto-config usually handles this via their own ordering). - **Framework infrastructure ordering:** ensuring some exporter/registrar is up before dependent beans. In all these, A does not call B — A relies on the *world* B changed. ## Why it's a last resort 1. **String-typed, not compile-checked.** `@DependsOn("driverRegistrar")` breaks silently if the bean is renamed; you find out at startup (or worse, only when the ordering matters). Injection is refactor-safe. 2. **Hides the real coupling.** The genuine dependency is on hidden global state. That global/static mutable state is often the deeper smell; `@DependsOn` papers over it rather than fixing it. 3. **No access.** It gives ordering but not a reference, so if you later need to call B you must add injection anyway. 4. **Graph pollution.** Overuse turns the implicit, self-describing injection graph into a set of hand-maintained ordering constraints that future readers can't derive from the code. ## The refactor that usually removes it Often you can convert the side effect into an explicit collaborator: instead of B mutating static state in `@PostConstruct` and A depending on that, make B expose the capability as a bean and inject it into A. Now the dependency is a real reference — typed, ordered automatically, and obvious. Reach for `@DependsOn` only when that refactor isn't feasible (legacy code, third-party static init, JVM-global effects). ## Contrast with sibling concerns - **`@Order`/`@Priority`:** order *within an injected collection* (a `List<Validator>`). Unrelated to instantiation timing. - **`@Lazy`:** defers creation until first use; can interact with `@DependsOn` (see edge-cases question) but solves a different problem. ## Interview soundbite "Injection expresses *what a bean needs*; `@DependsOn` expresses *what order things happen in* when there's no 'what it needs' to point at. Prefer the former; the latter is for non-injectable side effects and is inherently fragile because it's name-based."
- Give a case where @DependsOn genuinely cannot be replaced by injection.JVM-global or third-party static initialization you don't own: e.g. a bean that installs a JCA security provider or registers a legacy JDBC driver via a static call. The dependent bean needs that global effect done first but there's no object to inject, so ordering by name is the only lever.
- Why is the String-name nature of @DependsOn a maintenance risk?It's resolved at runtime, not compile time. Renaming or removing the target bean, or changing its default name, doesn't produce a compile error — it fails at startup with NoSuchBeanDefinitionException or, if the name accidentally still resolves elsewhere, produces wrong ordering silently. Injection references are checked by the compiler and IDE refactoring tools.
saying these in an interview costs you the question
- Using @DependsOn as the default way to order beans instead of injection.
- Claiming @DependsOn is type-safe/refactor-safe (it's String-based).
- Not recognizing that the underlying static/global state is often the real smell.