What are the different ways to inject dependencies in Spring, and why is constructor injection generally preferred?
Quick Answer
Spring supports constructor injection (dependencies passed via the constructor), setter injection (via public setter methods), and field injection (via @Autowired directly on a field). Constructor injection is generally preferred because it allows fields to be final and immutable, makes required dependencies explicit and impossible to construct in an invalid state, fails fast at startup if a dependency is missing, and is trivially testable without needing a Spring container or reflection-based mocking.
Detailed Answer
Spring supports three injection styles:
1. Constructor injection (recommended default):
@Service
class OrderService {
private final PaymentGateway gateway;
private final OrderRepository repository;
OrderService(PaymentGateway gateway, OrderRepository repository) { // @Autowired optional
this.gateway = gateway;
this.repository = repository;
}
}
2. Setter injection:
@Service
class OrderService {
private PaymentGateway gateway;
@Autowired
void setGateway(PaymentGateway gateway) { this.gateway = gateway; }
}
3. Field injection:
@Service
class OrderService {
@Autowired
private PaymentGateway gateway; // injected via reflection, bypassing the constructor entirely
}
Why constructor injection wins:
- Immutability: dependencies can be declared
final, guaranteeing they're set once and never reassigned. - Impossible to construct in an invalid state: you cannot create an
OrderServicewithout its required dependencies — the compiler enforces it, unlike field injection where a plainnew OrderService()compiles fine but produces an object withnulldependencies at runtime. - Fail-fast at startup: a missing or misconfigured dependency causes a clear
ApplicationContextstartup failure, rather than aNullPointerExceptiondeep in some unrelated code path later. - Trivial to unit test: you can construct the object directly with mocks in a plain JUnit test (
new OrderService(mockGateway, mockRepo)), with no Spring container and no reflection-based injection needed — field injection requires either a running context or reflection tricks (ReflectionTestUtils) to set the field for a test. - Surfaces excessive dependencies: a constructor with 8 parameters is an obvious, visible code smell suggesting the class does too much; field injection hides that same problem.
Since Spring 4.3, @Autowired is even optional on a constructor if the class has exactly one constructor — Spring infers it automatically, which is part of why constructor injection has become the idiomatic default in modern Spring code.