Constructor vs Setter Injection
Compare constructor and setter injection in Spring, their syntax and tradeoffs, and why constructor injection is the recommended default.
Introduction
Spring supports several ways to inject dependencies, but the two you will use in almost every project are constructor injection and setter injection. This lesson compares them directly — syntax, behavior, and tradeoffs — so you can make an informed choice instead of copying whatever style a tutorial happened to use.
Constructor Injection Syntax
With constructor injection, dependencies are declared as constructor parameters. Spring calls the constructor, supplying a matching bean for each parameter.
package com.programinds.demo;
import org.springframework.stereotype.Service;
@Servicepublic class PaymentService {
private final PaymentGateway paymentGateway;
public PaymentService(PaymentGateway paymentGateway) { this.paymentGateway = paymentGateway; }
public void charge(double amount) { paymentGateway.process(amount); }}Click Run to see what this code prints.
Setter Injection Syntax
With setter injection, Spring first creates the bean using a no-argument (or partially filled) constructor, then calls annotated setter methods to supply dependencies afterward.
package com.programinds.demo;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;
@Servicepublic class PaymentService {
private PaymentGateway paymentGateway;
@Autowired public void setPaymentGateway(PaymentGateway paymentGateway) { this.paymentGateway = paymentGateway; }
public void charge(double amount) { paymentGateway.process(amount); }}Click Run to see what this code prints.
Side-by-Side Comparison
| Aspect | Constructor Injection | Setter Injection |
|---|---|---|
| Immutability | Fields can be declared "final" | Fields cannot be "final" |
| Guaranteed initialization | Object is never in an incomplete state | Object can briefly exist without dependencies set |
| Required vs optional dependencies | Best for required dependencies | Better suited for optional dependencies |
| Circular dependency detection | Fails fast at startup | Can sometimes work around circular dependencies |
| Testability without Spring | Very easy — just call the constructor | Requires calling setters manually |
Why Constructor Injection Is Preferred
The official Spring documentation recommends constructor injection for mandatory dependencies because it produces immutable, fully initialized objects and makes dependencies explicit in the class's public API — you can see everything a class needs just by looking at its constructor signature.
Constructor injection also plays well with plain unit testing. You can instantiate PaymentService directly with a mock PaymentGateway in a test, with no Spring container involved at all — the constructor is just regular Java.
public class PaymentServiceTest {
public static void main(String[] args) { PaymentGateway fakeGateway = amount -> System.out.println("Fake charge: " + amount);
PaymentService service = new PaymentService(fakeGateway); service.charge(49.99); }}Click Run to see what this code prints.
When Setter Injection Still Makes Sense
Setter injection is not obsolete — it is genuinely useful for optional dependencies, where a sensible default exists and the dependency may or may not be provided. It is also occasionally used to break a genuine circular dependency between two beans, though redesigning the classes to avoid the cycle is usually a better fix.
Combining Both
Spring allows constructor injection for required dependencies and setter injection for optional ones within the same class, giving you the immutability benefits where they matter most while still allowing flexibility for optional collaborators.
@Servicepublic class PaymentService {
private final PaymentGateway paymentGateway; // required private FraudDetector fraudDetector; // optional
public PaymentService(PaymentGateway paymentGateway) { this.paymentGateway = paymentGateway; }
@Autowired(required = false) public void setFraudDetector(FraudDetector fraudDetector) { this.fraudDetector = fraudDetector; }
public void charge(double amount) { if (fraudDetector != null) { fraudDetector.check(amount); } paymentGateway.process(amount); }}Common Mistakes
- Using setter injection for required dependencies, which allows the object to exist in a half-initialized, invalid state.
- Mixing many optional setter dependencies into a class that would be simpler with fewer, required constructor dependencies.
- Forgetting that a single-constructor class does not need @Autowired at all in modern Spring — it is inferred automatically.
Best Practices
- Default to constructor injection for anything the class cannot function without.
- Reserve setter injection for genuinely optional collaborators with sensible defaults.
- Avoid field injection (@Autowired directly on a field) in production code entirely — prefer constructor or setter injection instead.
Frequently Asked Questions
No, you can combine constructor injection for required dependencies with setter injection for optional ones in the same class.
Only if the class has more than one constructor; with a single constructor, Spring infers it automatically since Spring 4.3.
Spring Boot's own starter classes overwhelmingly use constructor injection, reflecting the framework team's own recommendation.
Key Takeaways
- Constructor injection supplies dependencies at object creation time and supports immutable, "final" fields.
- Setter injection supplies dependencies after construction via setter methods, suited to optional dependencies.
- The Spring team recommends constructor injection for required dependencies.
- Both styles can be combined within a single class when appropriate.
Summary
Constructor injection wins for required dependencies because it guarantees a fully initialized, immutable object and keeps dependencies visible in the class's API; setter injection remains a good fit for optional collaborators. Next, we will step back and look closely at what a "bean" actually is and how beans get registered in and retrieved from the container.