← All articles

Constructor Injection vs Field Injection

2 min read

  • Spring Boot
  • Java

Almost every Spring tutorial written before about 2018 injects dependencies onto fields with @Autowired. Spring still supports it, but the framework's own documentation now recommends constructor injection. Here is why.

The three styles

@Service
public class OrderService {

    // 1. Field injection: avoid
    @Autowired
    private PaymentClient paymentClient;

    // 2. Setter injection: for genuinely optional dependencies
    @Autowired
    public void setPaymentClient(PaymentClient paymentClient) {
        this.paymentClient = paymentClient;
    }
}

// 3. Constructor injection: prefer this
@Service
public class OrderService {
    private final PaymentClient paymentClient;

    public OrderService(PaymentClient paymentClient) {
        this.paymentClient = paymentClient;
    }
}

Since Spring 4.3, a class with a single constructor does not need @Autowired on it at all. Spring will use it automatically.

Reason 1: The field cannot be final

With field injection, Spring constructs the object first and assigns the field afterwards through reflection. That means the field can never be final, so nothing stops it being reassigned later:

@Autowired
private PaymentClient paymentClient; // cannot be final

public void reset() {
    this.paymentClient = null; // compiles happily
}

Constructor injection gives you a final field, set once, never null.

Reason 2: Hidden dependencies

A constructor is a signature. It tells you exactly what the class needs. Field injection hides that. A class can quietly accumulate ten @Autowired fields and the constructor still reads new OrderService().

That is not just an aesthetic complaint. When the constructor gets uncomfortably long, that discomfort is the useful signal that the class is doing too much. Field injection removes the signal without removing the problem.

Reason 3: Testing without a Spring context

This is the one you feel day to day. With constructor injection, a unit test is plain Java:

@Test
void appliesDiscount() {
    var service = new OrderService(new FakePaymentClient());
    assertEquals(90, service.total());
}

With field injection there is no way in. You either start a Spring context (slow), or reach for reflection:

// The alternative nobody enjoys
ReflectionTestUtils.setField(service, "paymentClient", fake);

Reason 4: Circular dependencies fail loudly

If A needs B and B needs A, constructor injection cannot construct either one, so the application fails at startup with a clear BeanCurrentlyInCreationException.

Field injection often lets the same cycle start successfully and fail later at runtime, in production, under load. Spring Boot 2.6 and later disable circular references by default for exactly this reason.

An early crash is the better outcome. A cycle is a design problem, and startup is the cheapest moment to find out.

What about optional dependencies?

Genuinely optional collaborators are the one place setter injection still earns its keep:

@Service
public class ReportService {
    private final ReportRepository repo;
    private MetricsCollector metrics = MetricsCollector.noop();

    public ReportService(ReportRepository repo) {
        this.repo = repo;
    }

    @Autowired(required = false)
    public void setMetrics(MetricsCollector metrics) {
        this.metrics = metrics;
    }
}

Required dependencies go in the constructor. Optional ones get a sensible default and a setter.

If the constructor gets long

Lombok's @RequiredArgsConstructor generates it from the final fields:

@Service
@RequiredArgsConstructor
public class OrderService {
    private final PaymentClient paymentClient;
    private final OrderRepository orderRepository;
}

You keep final fields and testability without writing the boilerplate. Just remember the constructor still exists: if it is getting long, the class is still doing too much.