In the Spring Framework, dependency injection (DI) can be done in multiple ways. The two most common approaches are Constructor Injection and @Autowired (Field/Setter Injection). Here’s a clear, practical comparison with examples and best practices.
1️⃣ Constructor Injection (Recommended ✅)
What it is
Dependencies are provided through the class constructor.
Example
@Servicepublic class OrderService { private final PaymentService paymentService; public OrderService(PaymentService paymentService) { this.paymentService = paymentService; }}
Since Spring 4.3+, if there’s only one constructor, you don’t even need
@Autowired.
✅ Advantages
- Immutability
Dependencies can befinal, making the class thread-safe and predictable. - Clear dependencies
Anyone reading the constructor knows exactly what the class needs. - Fail-fast
Application fails at startup if a dependency is missing. - Best for testing
Easy to inject mocks without Spring:new OrderService(mockPaymentService); - Recommended by Spring & Clean Code principles
❌ Disadvantages
- Constructor can become large if there are too many dependencies
→ Usually a sign that the class violates Single Responsibility Principle (SRP).
2️⃣ @Autowired Field Injection
What it is
Dependencies are injected directly into fields.
Example
@Servicepublic class OrderService { @Autowired private PaymentService paymentService;}
✅ Advantages
- Less boilerplate
- Quick and easy for small demos or POCs
❌ Disadvantages
- Hidden dependencies
Not visible via constructor. - Harder to unit test
Requires reflection or Spring context. - Cannot use
finalfields - Violates immutability
- Makes the class tightly coupled to Spring
3️⃣ @Autowired Setter Injection (Less Common)
Example
@Servicepublic class OrderService { private PaymentService paymentService; @Autowired public void setPaymentService(PaymentService paymentService) { this.paymentService = paymentService; }}
When to use
- Optional dependencies
- Circular dependency edge cases (use sparingly)
🔍 Side-by-Side Comparison
| Aspect | Constructor Injection | @Autowired Field |
|---|---|---|
| Recommended by Spring | ✅ Yes | ❌ No |
| Immutability | ✅ Yes | ❌ No |
| Unit testing | ✅ Easy | ❌ Difficult |
| Dependencies visibility | ✅ Explicit | ❌ Hidden |
final fields | ✅ Yes | ❌ No |
| Clean architecture | ✅ Strong | ❌ Weak |
🏆 Best Practice (Industry Standard)
👉 Always prefer Constructor Injection
Spring official docs, Google, and large-scale enterprise systems (banking, fintech, cloud-native apps) all follow this pattern because it:
- Improves maintainability
- Prevents runtime surprises
- Aligns with SOLID principles
✅ Recommended Pattern (Modern Spring)
@Servicepublic class OrderService { private final PaymentService paymentService; private final AuditService auditService; public OrderService(PaymentService paymentService, AuditService auditService) { this.paymentService = paymentService; this.auditService = auditService; }}
