What are the practical pitfalls of context hierarchies, and when would you deliberately choose a hierarchy over a single flat context?
answer
- Overlapping scans → duplicate singletons
- Wrong copy injected → @Transactional/AOP silently dead
- Shadowing hides your 'shared' edits
- getBeansOfType ≠ ancestors; @Autowired List = ancestors
- Use hierarchy for multi-servlet / bootstrap / tenant isolation; else flat
basics
~20 sPitfalls: duplicate singletons from overlapping scans, accidental name-shadowing, getBeansOfType not seeing ancestors, and AOP/transactions breaking on the wrong copy. Use a hierarchy only to share one root layer across several isolated children (e.g., multiple DispatcherServlets); otherwise prefer a single flat context.
solid answer
~40 sHierarchies add real cost, so use them deliberately. The main pitfalls: (1) **duplicate singletons** — the same component scanned in both levels yields two instances, so shared state/caches diverge; (2) a controller injecting the *un-proxied* copy of a service means **AOP/@Transactional silently stop applying**; (3) **name shadowing** where a child bean overrides a parent you thought you were reconfiguring; (4) the **getBeansOfType-excludes-ancestors** surprise; (5) lifecycle ordering — parent must refresh first, child close first. Choose a hierarchy when you genuinely need **one shared root reused by multiple isolated children**: several `DispatcherServlet`s over shared services, Spring Cloud's bootstrap context, or plugin/tenant isolation where each child adds beans siblings shouldn't see. For the common single-web-app case, a **flat Boot context** is simpler and avoids every pitfall above — which is exactly why Boot defaults to it.
go deeper
Know that hierarchies can create duplicate beans and that flat is simpler for typical apps.
Explain the overlapping-scan duplicate-singleton pitfall and its link to broken transactions/AOP.
Enumerate the main pitfalls (duplicates, shadowing, getBeansOfType gap, lifecycle) and give concrete justified use-cases vs flat.
Make the architectural call: quantify the isolation requirement, weigh operational/debugging cost, and default to a single context unless multi-child sharing is real.
## Why hierarchies are costly A parent-child arrangement buys **sharing + isolation**, but each seam introduces failure modes that a single flat context simply cannot have. ### Pitfall 1 — Duplicate singletons from overlapping component scans If both the parent and a child `@ComponentScan` the same packages, each factory instantiates its **own** singleton of every matched bean. You now have two `UserService` objects. Any in-memory state, cache, counter, or `@Scheduled` you assumed was global is duplicated. Fix: partition scans by stereotype (root scans `@Service`/`@Repository` and excludes `@Controller`; the web child scans only `@Controller`). ### Pitfall 2 — Broken AOP / transactions Proxies (`@Transactional`, `@Async`, custom aspects) are created **in the context that owns the bean**. If a controller in the child injects a *second, un-proxied* copy of a service that also exists in the parent, the transactional advice never runs — a subtle, data-corrupting bug. Same root cause as pitfall 1. ### Pitfall 3 — Accidental name shadowing A same-named child bean shadows the parent for child lookups. You edit the 'shared' bean's config in the root and see no effect because a child override quietly wins for that servlet. ### Pitfall 4 — Collection lookups don't span ancestors `getBeansOfType`, `getBeanNamesForType`, `getBeanDefinitionNames` introspect only the local factory. Code that enumerates beans (e.g., a registry that auto-collects all `Validator`s) misses ancestor beans unless it uses `BeanFactoryUtils.*IncludingAncestors`. (Note `@Autowired List<T>` *does* span ancestors, so the two views disagree.) ### Pitfall 5 — Lifecycle & events Parent must be refreshed before the child; the child should be closed before the parent (parent close doesn't cascade to children). Events only bubble **up** (child→parent) since 4.2, never down — so a parent-level publisher can't notify child listeners. ## When a hierarchy is the right call - **Multiple `DispatcherServlet`s** sharing one root of business beans (a UI servlet + a REST servlet). - **Spring Cloud bootstrap context**: config/encryption beans in a parent bootstrap context, application beans in the child. - **Plugin / multi-tenant isolation**: a shared core parent with per-plugin or per-tenant child contexts that add beans siblings must not see. - **Test slicing / embedding**: embedding an app context as a child of a host container's context. ## When NOT to The ordinary single-web-application, single-`DispatcherServlet` case. A **flat Spring Boot context** eliminates every pitfall above, keeps bean resolution obvious, and is the default for a reason. Reach for a hierarchy only when the sharing-with-isolation requirement is concrete — otherwise the complexity isn't paying rent.
- A metrics registry auto-collects beans via getBeansOfType and misses beans defined in the parent context. Why, and how do you fix it?getBeansOfType introspects only the local factory and excludes ancestors. Switch to BeanFactoryUtils.beansOfTypeIncludingAncestors, or inject the collection with @Autowired (which spans ancestors), or define the collected beans in the same context as the registry.
- Give one concrete case where a hierarchy is clearly justified.Two DispatcherServlets — one serving the HTML UI, one serving /api as REST — sharing a single root context of services, repositories, and the datasource, while each keeps its own web config (view resolvers, converters, interceptors).
saying these in an interview costs you the question
- Reaching for a context hierarchy by default instead of a flat context.
- Not realizing overlapping scans create duplicate singletons that break AOP/transactions.
- Assuming enumerating beans (getBeansOfType) transparently includes parent beans.