Dependency Injection in Spring
Spring's IoC container wires collaborators at startup. Prefer constructor injection for required dependencies — it enables immutability, testability, and fail-fast wiring.
Deep dive
What it really is
Spring builds a graph of singleton beans during ApplicationContext startup. Each bean's dependencies are resolved by type (and disambiguated by @Qualifier or bean name). Constructor injection is the only style that makes a bean mandatory and immutable.
Why constructor over field
- Detects cycles and missing beans at startup, not at first call.
- Lets you mark fields
finaland instantiate the bean in tests withnew. - Plays nicely with
@RequiredArgsConstructor(Lombok) and Java records.
Scopes
singleton (default), prototype, request, session. Misusing prototype inside singleton requires ObjectProvider or @Lookup — otherwise the prototype is captured once and never re-created.
Real-world example
From productionMigrating a 200k-LOC monolith from field injection to constructor injection surfaced 14 circular dependencies that had silently worked because Spring lazily resolved fields. Three of them were latent NPEs hit only at peak load. The refactor itself was mechanical; the value was the surfaced design debt.
Interview questions
3 senior-levelQ1Field vs constructor injection — which and why?▾
Constructor for required collaborators, setter for optional reconfiguration, never field outside tests. Constructor enables immutability, fails fast on missing beans, exposes cycles, and lets the class be instantiated without Spring in unit tests.
Q2How does Spring resolve two beans of the same type?▾
By name (parameter name matches bean name), @Qualifier, @Primary, or @Profile. If still ambiguous, startup fails with NoUniqueBeanDefinitionException. I prefer @Qualifier with a constant — it's explicit and refactor-safe.
Q3Why is injecting a prototype bean into a singleton risky?▾
The prototype is resolved once at wiring time and cached, defeating the scope. Fix with ObjectProvider<T>, Provider<T>, or method injection via @Lookup.
Common mistakes
Using
@Autowiredon fields, then wondering why tests need a full context.Hiding cycles with
@Lazyinstead of fixing the design.Component-scanning the entire
compackage, slowing startup and pulling in test fixtures.
Trade-offs
Constructor injection forces you to deal with cycles upfront — that's a feature, not a bug.
Heavy use of
@Conditionalmakes context startup behaviour environment-dependent; document it.