Spring Fundamentals
Dependency Injection — Constructor vs Field vs Setter
Compare injection strategies and understand why constructor injection is preferred in production.
Interview: Interviews frequently drill down on why constructor injection is preferred over field/setter injection, focusing on immutability, circular dependencies, and testability.
Introduction
Dependency Injection (DI) is the concrete implementation of the Inversion of Control (IoC) principle. It is the process by which objects define their dependencies (the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed. The IoC container then injects those dependencies when it creates the bean.
Spring supports three main types of Dependency Injection: Constructor Injection, Setter Injection, and Field Injection.
Why It Matters
Choosing the right injection method directly impacts your application's architecture, maintainability, and testing. Poor injection decisions can lead to hidden dependencies, difficulty in unit testing (requiring slow Spring context bootstrapping), mutable states that are unsafe in multi-threaded environments, and circular dependencies that only blow up at runtime instead of failing fast.
Real-World Analogy
Think of the three injection types like different ways of assembling a **smartphone**:
- Constructor Injection: The phone is manufactured with a sealed, non-removable battery inside. The phone cannot exist or function without the battery; it is completely assembled and immutable from day one.
- Setter Injection: The phone has a battery compartment with a removable back cover. You can purchase the phone carcass, insert a battery later, or swap it with a different battery whenever you want. It is mutable.
- Field Injection: The battery is magically teleported into the phone's sealed body using a secret access hatch (reflection). You do not see any battery slot, and you cannot install a battery yourself without using specialized reflection tools (a Spring context).
Comparison Matrix
| Criteria | Constructor Injection | Field Injection | Setter Injection |
|---|---|---|---|
| Immutability | Yes (allows final fields) | No (cannot use final) | No (cannot use final) |
| Testability | Excellent (simple new keyword) | Poor (requires Mockito/Spring) | Good (via setter methods) |
| Circular Dependency | Fail-fast (Startup failure) | Delayed (Runtime failure) | Delayed (Runtime failure) |
| Spring Coupling | None (no annotations required) | High (tightly bound to @Autowired) | Low (optional @Autowired) |
Practical Examples
Let's see how each of these three styles is implemented in Java:
1. Constructor Injection (Recommended)
2. Field Injection (Anti-pattern)
3. Setter Injection (Optional / Mutable dependencies)
Quick Quiz
Q1: Why is Field Injection considered an anti-pattern for testing?
A) It makes the code run slower in production.
B) It requires using reflection to inject mocks in unit tests, preventing instantiation using the new keyword.
C) It uses too much heap memory.
D) It only works with XML configurations.
Answer: B — Field injection locks your code to the Spring container, making plain unit testing hard because you cannot instantiate classes manually with their dependencies.
Q2: What happens when a circular dependency occurs with Constructor Injection?
A) Spring resolves it automatically by creating a proxy.
B) Spring starts successfully, but throws a NullPointerException during request processing.
C) Spring fails to start and immediately throws a BeanCurrentlyInCreationException at startup.
D) The JVM crashes due to OutOfMemoryError.
Answer: C — Constructor injection forces Spring to check dependency resolution at instantiation time, triggering a fail-fast startup crash rather than letting the application boot with unstable circular graphs.
Scenario-Based Challenge
Production Scenario:
You have a class with 15 mandatory dependencies and 3 optional ones. How would you design your dependency injection approach to maintain clean, readable code and ensure thread safety?
View Solution
First, having 15 mandatory dependencies is a major code smell violating the Single Responsibility Principle (SRP). You should refactor this class into smaller, cohesive services. Once refactored, use Constructor Injection for the mandatory dependencies (using final fields) and Setter Injection for the optional ones. To keep constructor boilerplate low, use Lombok's @RequiredArgsConstructor annotation.
Interview Questions
1. Conceptual: Why does Constructor Injection enforce bean immutability?
Because dependencies are injected at creation time, the target fields can be declared as final. This guarantees that once the bean is initialized by the container, its dependencies cannot be altered or re-assigned, ensuring thread safety in multi-threaded application environments.
2. Technical: How does the @Autowired annotation work under the hood in Spring?
Spring uses a specialized BeanPostProcessor implementation called AutowiredAnnotationBeanPostProcessor. During the merge/post-process phase of bean creation, it uses reflection to scan fields, constructors, and methods for @Autowired, resolves the matching beans from the container context, and injects them directly.
Production Considerations
In production environments, always default to **Constructor Injection**. Combine this with Lombok's @RequiredArgsConstructor to eliminate boilerplate constructor definitions. This guarantees that class fields are immutable, dependencies are explicitly defined, and the code remains completely decoupled from Spring frameworks in unit tests.