How do you set a depends-on relationship programmatically on a BeanDefinition, and how would you decide between @DependsOn and alternative ordering mechanisms in framework/infrastructure code?
answer
- annotation → BeanDefinition.setDependsOn(String...)
- BeanDefinitionBuilder.addDependsOn / getDependsOn
- stamp via BeanFactoryPostProcessor at scale
- Spring JPA/DataSource ordering uses it
- centralize, don't scatter string names
basics
~10 sOn a BeanDefinition call setDependsOn(String...) (e.g. via BeanDefinitionBuilder.addDependsOn or an AbstractBeanDefinition), or in XML use depends-on. It's the programmatic equivalent of @DependsOn, used by infrastructure/registrars where annotations aren't available.
solid answer
~40 sThe annotation @DependsOn is just metadata that Spring translates into BeanDefinition.setDependsOn(String...). When you register beans programmatically — a BeanDefinitionRegistryPostProcessor, ImportBeanDefinitionRegistrar, or a FactoryBean-style config — you set ordering directly: BeanDefinitionBuilder.genericBeanDefinition(X.class).addDependsOn("infra").getBeanDefinition(), or call setDependsOn on an AbstractBeanDefinition/RootBeanDefinition, then register via BeanDefinitionRegistry.registerBeanDefinition. getDependsOn() reads it back. This is how framework code (and auto-configurations) enforce that infrastructure beans — e.g. a DataSource initializer, an EntityManagerFactory, a Flyway migrator — precede consumers when there's no injection edge. When choosing: prefer real injection edges; use depends-on for non-injectable side effects; for consistent ordering of many beans relative to one piece of infrastructure, a BeanFactoryPostProcessor that stamps depends-on onto matching definitions (as Spring's own JPA/JTA support does) scales better than scattering annotations.
code
java · 20 lines// Stamp depends-on onto every consumer of an infrastructure bean, centrally
public class InfraOrderingPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
for (String name : bf.getBeanNamesForType(DbConsumer.class)) {
BeanDefinition bd = bf.getBeanDefinition(name);
String[] existing = bd.getDependsOn();
List<String> deps = existing == null
? new ArrayList<>() : new ArrayList<>(Arrays.asList(existing));
if (!deps.contains("schemaInitializer")) deps.add("schemaInitializer");
bd.setDependsOn(deps.toArray(new String[0])); // == @DependsOn("schemaInitializer")
}
}
}
// Equivalent for a freshly-built definition:
BeanDefinition bd = BeanDefinitionBuilder
.genericBeanDefinition(ReportService.class)
.addDependsOn("driverRegistrar")
.getBeanDefinition();go deeper
Aware the XML depends-on attribute exists as the non-annotation form.
Know @DependsOn maps to BeanDefinition.setDependsOn and can be set in code.
Can set it via BeanDefinitionBuilder/AbstractBeanDefinition in a registrar/post-processor and read it back.
Chooses between per-bean annotation and a centralized BeanFactoryPostProcessor policy, knows the phase constraint, and relates it to how Spring orders infrastructure like the EntityManagerFactory.
## The annotation is sugar over BeanDefinition metadata `@DependsOn` carries no runtime magic of its own — during bean-definition processing Spring reads it and calls `AbstractBeanDefinition.setDependsOn(String...)`. Everything the annotation does is expressible on the `BeanDefinition` directly, which is what you use when there's no class to annotate (dynamic registration, third-party beans, infrastructure). ### Setting it programmatically **Via `BeanDefinitionBuilder`:** ```java BeanDefinition bd = BeanDefinitionBuilder .genericBeanDefinition(ReportService.class) .addDependsOn("driverRegistrar") // one call per name, or use setDependsOn .getBeanDefinition(); registry.registerBeanDefinition("reportService", bd); ``` **Directly on an `AbstractBeanDefinition` / `RootBeanDefinition` / `GenericBeanDefinition`:** ```java GenericBeanDefinition bd = new GenericBeanDefinition(); bd.setBeanClass(ReportService.class); bd.setDependsOn("driverRegistrar", "cacheSeeder"); String[] current = bd.getDependsOn(); // read back ``` **Where you'd do this:** - `BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry` — mutate/add definitions before instantiation. - `ImportBeanDefinitionRegistrar` — register beans from an `@Import`/annotation-driven feature. - A `BeanFactoryPostProcessor` that *stamps* `depends-on` onto a set of existing definitions matching some criterion. **XML equivalent:** `<bean id="reportService" class="..." depends-on="driverRegistrar,cacheSeeder"/>`. ## How Spring itself uses this Spring's own infrastructure uses programmatic depends-on to guarantee ordering without injection edges. A well-known pattern: JPA support ensures beans that use persistence are created after the `EntityManagerFactory`/`DataSource` is ready; support classes register or post-process definitions to add the appropriate depends-on so, e.g., a Hibernate `SessionFactory`/schema init precedes consumers. Auto-configurations do similar stamping so that, for instance, database-touching beans follow the schema initializer. The point: at framework scale you often can't rely on every consumer injecting the infrastructure, so you enforce ordering centrally on the definitions. ## Decision framework (principal-level) 1. **Is there a real reference?** If A uses B's API, inject it. Type-safe, self-ordering, refactor-proof. Never use depends-on here. 2. **Is it a one-off non-injectable side effect?** A single `@DependsOn` on the consumer is fine and readable. 3. **Is it 'everything of kind X must follow infrastructure Y'?** Don't scatter annotations across dozens of consumers — write a `BeanFactoryPostProcessor`/`BeanDefinitionRegistryPostProcessor` that stamps `setDependsOn("Y")` onto every matching definition. Centralized, consistent, and it also works for beans you don't own or that are registered dynamically. 4. **Is the need actually collection ordering** (a `List<X>` in a deterministic order)? That's `@Order`/`Ordered`/`@Priority`, not depends-on. 5. **Is the need to defer creation / break a startup cost?** That's `@Lazy`, orthogonal — but remember depends-on can force a lazy target eager. ## Pitfalls at scale - **String coupling everywhere** becomes unmaintainable; centralize when it's a pattern rather than a one-off. - **Hidden ordering** that readers can't derive from injection — document why the depends-on exists. - **Over-forcing eager init** of otherwise-lazy infrastructure, hurting startup time; measure. - **Cycles are easier to create** when stamping programmatically across many beans — guard against introducing an infrastructure↔consumer loop. ## Soundbite "`@DependsOn` is annotation sugar for `BeanDefinition.setDependsOn`. In framework code you set it programmatically — often via a `BeanFactoryPostProcessor` that stamps ordering onto a whole class of definitions — which is exactly how Spring guarantees infrastructure like the `EntityManagerFactory` precedes its consumers without an injection edge."
- Why might a BeanFactoryPostProcessor that stamps depends-on be better than annotating each bean?It centralizes the ordering rule ('all DB consumers follow the schema initializer') in one place, applies uniformly including to beans you don't own or that are registered dynamically, avoids scattering fragile string names across the codebase, and is easy to audit and change. Annotations are fine for one-offs but don't scale to a cross-cutting ordering policy.
- At which container phase must depends-on be set for it to take effect?During bean-definition processing, before instantiation — i.e. in a BeanFactoryPostProcessor / BeanDefinitionRegistryPostProcessor, or as static metadata via the annotation/XML. Once the container starts instantiating singletons, the dependency graph is already consumed, so mutating setDependsOn afterward has no effect on ordering.
saying these in an interview costs you the question
- Believing @DependsOn has runtime behavior beyond setting BeanDefinition metadata.
- Trying to change ordering by mutating setDependsOn after beans are already being instantiated.
- Scattering string-name depends-on across many beans instead of a centralized post-processor for a cross-cutting rule.