What are the different ways to inject dependencies in Spring, and why is constructor injection generally preferred?

8 minintermediatedependency-injectionconstructor-injectionbest-practices

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: it lets fields 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 OrderService without its required dependencies. The compiler enforces it. Field injection is different — a plain new OrderService() compiles fine but produces an object with null dependencies at runtime.
  • Fail-fast at startup: a missing or misconfigured dependency causes a clear ApplicationContext startup failure, instead of a NullPointerException deep 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)). 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. That's part of why constructor injection has become the idiomatic default in modern Spring code.