ABHIJAT
← Back to Writing

Backend

Why I stopped using field injection in Spring Boot

`@Autowired` on a field is the fastest way to wire a dependency and the easiest way to hide a circular dependency until it's a runtime surprise.

Abhijat2026-083 min read5 views

Last updated September 17, 2026

@Autowired directly on a field is the shortest way to wire a Spring bean, and it's the pattern most tutorials lead with — which is part of why it's still common in codebases where it causes real problems.

The visibility problem

A class using field injection doesn't expose its dependencies anywhere in its public API — they're private fields, populated by reflection after construction, invisible to anyone reading the class's signature or trying to instantiate it outside of Spring's container.

@Service
public class OrderService {
    @Autowired
    private PaymentClient paymentClient;
    @Autowired
    private InventoryService inventoryService;
}

There's no way to tell, from the outside, what OrderService needs to function. Testing it means either standing up a Spring context or reaching for reflection-based mocking, because there's no constructor to pass test doubles into.

Constructor injection fixes both problems

@Service
public class OrderService {
    private final PaymentClient paymentClient;
    private final InventoryService inventoryService;

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

Now the dependencies are visible in the constructor's signature, the fields can be final (the class genuinely can't be constructed in an invalid state), and a unit test can just call new OrderService(fakePaymentClient, fakeInventoryService) with no Spring context required at all.

The bug it hides

Field injection also hides circular dependencies until runtime, because Spring can partially construct both beans before wiring the fields — with constructor injection, a circular dependency fails immediately and loudly at startup, because there's no way to call two constructors that each need the other's fully-built instance first. That's a strictly better failure mode: a startup error you fix once, instead of a subtle runtime issue that only surfaces under a specific initialization order.

The migration is mechanical enough that it's worth doing even in an existing codebase — Spring itself has recommended constructor injection as the default for years, for exactly these reasons.

Tags

Spring BootJavaBackend