Choosing Between Constructor and Setter Injection in Spring
A technical guide on choosing between Constructor and Setter injection in Spring, focusing on immutability, circular dependency detection, and architectural best practices.
03 Jan 2026, 17:20 UTC

The Dependency Decision: Immutability vs. Flexibility
When designing Spring beans, the primary technical challenge is ensuring that a component has all the required dependencies to function correctly without introducing tight coupling or unstable states. The decision between constructor injection and setter injection determines whether a bean is initialized as a complete, immutable object or as a mutable object that can be configured over time.
The critical takeaway: Use Constructor Injection for all mandatory dependencies to guarantee object validity and immutability. Use Setter Injection only for optional dependencies or properties that must change during the application's lifecycle.
Comparison of Injection Strategies
| Feature | Constructor Injection | Setter Injection |
|---|---|---|
| Dependency Requirement | Mandatory | Optional / Defaultable |
| Immutability | Supported (via final fields) |
Not supported |
| Startup Validation | Fails immediately on missing deps | May fail later (NullPointerException) |
| Circular Dependencies | Detected at startup (Throws exception) | Permitted (but often a design smell) |
| Unit Testing | Simple (Plain Java instantiation) | Requires calling multiple setters |
Engineering Trade-offs
The Case for Constructor Injection
Constructor injection ensures that a bean is never instantiated in an incomplete state. By declaring dependency fields as final, you prevent the state from being altered after the Spring container has initialized the bean. This is essential for thread-safety in singleton-scoped beans.
From a testing perspective, constructor injection removes the need for a Spring ApplicationContext or reflection-based mocking frameworks during unit tests. You can simply pass mock objects into the constructor using new MyService(mockRepo).
The Case for Setter Injection
Setter injection is useful when a dependency is truly optional—meaning the class has a sensible default behavior if the dependency is missing. It also allows for the reconfiguration of a bean at runtime without requiring the container to destroy and recreate the instance.
The Circular Dependency Trap
A circular dependency occurs when Bean A requires Bean B, and Bean B requires Bean A. Constructor injection detects this at startup because neither bean can be fully instantiated before the other. While this results in a BeanCurrentlyInCreationException, it is generally considered a positive outcome as it forces the developer to resolve the architectural flaw (e.g., by introducing a third bean or using an event-driven approach) rather than allowing a fragile runtime state.
Implementation Example
The following example demonstrates the recommended pattern for a mandatory service dependency and an optional configuration dependency in Spring 4.3+.
@Service
public class OrderProcessingService {
// Mandatory dependency: marked final for immutability
private final PaymentGateway paymentGateway;
// Optional dependency: can be changed or left as null
private NotificationService notificationService;
// Spring 4.3+ automatically injects this if it is the only constructor
public OrderProcessingService(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
@Autowired
public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void processOrder(Order order) {
paymentGateway.charge(order);
if (notificationService != null) {
notificationService.sendConfirmation(order);
}
}
}
Validation and Diagnostics
To verify your injection strategy is working as intended, perform the following checks:
- Immutability Check: Attempt to change a
finalfield within a method in your service. The Java compiler should prevent this, confirming the bean's state is locked after construction. - Startup Failure Test: Temporarily create a circular dependency between two constructor-injected beans. Run the application; Spring should throw a
BeanCurrentlyInCreationExceptionduring the startup sequence, confirming that the container is validating the dependency graph. - Nullability Check: For setter-injected beans, ensure your logic handles
null(as seen in theprocessOrderexample above) to preventNullPointerExceptionwhen the optional dependency is not provided in the context.
Limitations
Constructor injection can lead to "Constructor Bloat" if a class has too many dependencies (typically more than 5). If you find your constructor growing excessively, this is a diagnostic signal that the class is violating the Single Responsibility Principle and should be split into smaller, more focused components.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.