Spring DI: Constructor vs @Autowired Injection Explained

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

@Service
public 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 be final, 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

@Service
public 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 final fields
  • Violates immutability
  • Makes the class tightly coupled to Spring

3️⃣ @Autowired Setter Injection (Less Common)

Example

@Service
public 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

AspectConstructor 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)

@Service
public class OrderService {
private final PaymentService paymentService;
private final AuditService auditService;
public OrderService(PaymentService paymentService,
AuditService auditService) {
this.paymentService = paymentService;
this.auditService = auditService;
}
}

Leave a Reply