How does bean resolution delegate from a child context to its parent, and what happens when the child defines a bean with the same name as one in the parent?
answer
- doGetBean: local first, then getParentBeanFactory()
- Same name in child = shadows parent (no error)
- One name → two instances (per context)
- getBean spans ancestors; getBeansOfType does NOT
- @Autowired collections DO span ancestors
basics
~20 sWhen the child can't find a bean locally, it asks its parent (delegation upward). If the child defines a bean with the same name as the parent, the child's bean 'hides' the parent's for lookups made from the child — both instances can still exist independently.
solid answer
~40 sEvery context wraps a `HierarchicalBeanFactory`. On `getBean("x")`, `AbstractBeanFactory.doGetBean` checks the child's own bean definitions first; if absent, it calls `getParentBeanFactory().getBean("x")` and walks upward. So single-bean lookups (by name or by type) transparently span the hierarchy. When the child declares a bean with the **same name** as the parent, there's no error — the child's definition simply **shadows** the parent's for anything resolved through the child. The parent keeps and uses its own instance; the child uses its override. That means the same singleton name can back **two distinct instances**, one per context. This is a deliberate override technique (e.g., a DispatcherServlet child overriding a root bean). A subtle asymmetry: `getBean` delegates to ancestors, but `getBeansOfType(...)` on a context does **not** include ancestor beans by default.
code
java · 25 lines@Configuration
class ParentConfig {
@Bean Clock clock() { return Clock.systemUTC(); } // parent version
}
@Configuration
class ChildConfig {
@Bean Clock clock() { return Clock.fixed(EPOCH, UTC); } // shadows parent for child
}
var parent = new AnnotationConfigApplicationContext(ParentConfig.class);
var child = new AnnotationConfigApplicationContext();
child.setParent(parent);
child.register(ChildConfig.class);
child.refresh();
child.getBean(Clock.class); // -> fixed clock (local wins)
parent.getBean(Clock.class); // -> systemUTC() (parent never sees child)
// Asymmetry: getBean delegates up, getBeansOfType does not
var child2 = new AnnotationConfigApplicationContext();
child2.setParent(parent);
child2.refresh();
child2.getBean(Clock.class); // OK - found in parent
child2.getBeansOfType(Clock.class); // {} empty - ancestors not includedgo deeper
Explain that the child asks the parent when it can't find a bean, and that a same-named child bean 'wins' locally.
Describe doGetBean's local-then-parent delegation and the shadowing semantics producing per-context instances.
Call out the getBean-vs-getBeansOfType ancestor asymmetry and that @Autowired collections include ancestors while getBeansOfType does not.
Reason about failure modes: duplicated singletons/state, accidental shadowing hiding config changes, and where to place shared dependencies given upward-only wiring.
## Delegation mechanics The container behind every `ApplicationContext` implements `HierarchicalBeanFactory`, which adds two methods over the plain `BeanFactory`: - `getParentBeanFactory()` — returns the parent factory (or `null`). - `containsLocalBean(name)` — true only if the bean exists **in this factory**, ignoring ancestors. On a lookup, `AbstractBeanFactory.doGetBean(name, ...)`: 1. Tries to resolve `name` **locally** (own singleton cache + own bean definitions). 2. If not found **and** a parent factory exists, it delegates: `parentBeanFactory.getBean(name, ...)`. 3. This repeats up the chain until resolved or the root throws `NoSuchBeanDefinitionException`. So **single-bean lookups traverse the hierarchy**: `getBean(String)`, `getBean(Class)`, and `@Autowired` single-dependency injection all consider ancestor beans (autowiring uses `BeanFactoryUtils.beanNamesForTypeIncludingAncestors`). ## Same-name override (shadowing) If the child registers a bean with the **same name** as one in the parent: - It is **not** a conflict/error — the two live in different factories. - Lookups from the **child** return the **child's** bean (local wins before delegation). - Lookups from the **parent** return the **parent's** bean (parent never consults the child). - Result: the same logical name can back **two separate instances**, one per context. Each context's beans wire against the version visible to *them*. This is a legitimate override pattern: put a default in the root, override it in a specific child. ## The `getBeansOfType` asymmetry (key gotcha) While `getBean` delegates upward, **`ApplicationContext.getBeansOfType(...)` does NOT include ancestor contexts by default** — it introspects only the local factory's beans. To span the hierarchy for a *collection* lookup you must use `BeanFactoryUtils.beansOfTypeIncludingAncestors(factory, type)`. This surprises people: `getBean(Foo.class)` finds a parent bean, but `getBeansOfType(Foo.class)` on the same child may return an empty map. Note: **`@Autowired` collection injection** (e.g. `List<Foo>`) *does* include ancestors, because `DefaultListableBeanFactory.findAutowireCandidates` uses the `...IncludingAncestors` helper. So the collection you get via autowiring can differ from what `getBeansOfType` returns. ## Practical implications - **Duplicated singletons:** the same `@Component` type scanned in both parent and child yields two instances — watch for state/caches you assumed were global. - **Wiring targets:** a parent bean can only inject other parent (ancestor) beans; it can never depend on a child bean, so keep shared dependencies at or above the level that needs them. - **Override with care:** shadowing is powerful but can hide bugs where you *thought* you were reconfiguring the shared bean but only overrode it in one child.
- Why can getBean(Foo.class) succeed while getBeansOfType(Foo.class) returns empty on the same child?getBean does single-bean resolution and delegates to the parent factory when the bean is missing locally. getBeansOfType introspects only the local factory and excludes ancestors by default; use BeanFactoryUtils.beansOfTypeIncludingAncestors to span the hierarchy.
- If the same @Component class is scanned in both parent and child, how many instances exist?Two — each context creates its own singleton in its own factory. They are independent objects; sharing requires defining the bean only in the parent and injecting it into the child.
saying these in an interview costs you the question
- Saying a duplicate bean name across levels throws an error — it doesn't; the child shadows the parent.
- Assuming getBeansOfType automatically includes parent beans (it doesn't by default).
- Thinking a same-named singleton is one shared instance across both contexts.