Skip to content
Back to Blog

Backend Engineering

Why I Stopped Using @Autowired Everywhere

Field injection feels convenient until your tests become a nightmare. Here's why constructor injection isn't just a preference—it's a design decision that pays off.

Gautam ThapaJan 21, 20266

Every Spring Boot tutorial starts the same way:

@Autowired
private UserService userService;

It works. It's concise. And it's a trap.

After years of debugging production issues and writing tests for legacy codebases, I've stopped using field injection entirely. Here's why.

The Problem With Field Injection

Field injection hides your dependencies. When you look at a class constructor, you should immediately understand what that class needs to function:

// Field injection - dependencies are hidden
@Service
public class OrderService {
    @Autowired
    private UserService userService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private NotificationService notificationService;
    
    @Autowired
    private AuditService auditService;
}

Five dependencies. You only discover this by scanning the entire class. Now imagine this class has 500 lines of code.

Constructor Injection Makes Dependencies Explicit

@Service
public class OrderService {
    private final UserService userService;
    private final PaymentService paymentService;
    private final InventoryService inventoryService;

    public OrderService(
            UserService userService,
            PaymentService paymentService,
            InventoryService inventoryService
    ) {
        this.userService = userService;
        this.paymentService = paymentService;
        this.inventoryService = inventoryService;
    }
}

Now the constructor screams: "This class does too much." That's valuable feedback.

Testing Becomes Trivial

With field injection, you need reflection or Spring's test context to inject mocks:

// Painful
@SpringBootTest
class OrderServiceTest {
    @MockBean
    private UserService userService;
    // ... slow, heavy tests
}

With constructor injection:

// Simple
class OrderServiceTest {
    private OrderService orderService;

    @BeforeEach
    void setUp() {
        orderService = new OrderService(
            mock(UserService.class),
            mock(PaymentService.class),
            mock(InventoryService.class)
        );
    }
}

No Spring context. No reflection. Fast, isolated unit tests.

The Final Keyword Matters

Notice the final keyword on each field. This guarantees:

  • Dependencies are set once at construction
  • No accidental reassignment
  • Thread safety without synchronization
  • Compiler enforces completeness

When I Still Use @Autowired

Setter injection for optional dependencies:

@Autowired(required = false)
public void setMetricsService(MetricsService metricsService) {
    this.metricsService = metricsService;
}

That's it. Everything else gets constructor injection.

The Rule

If your constructor has more than 4-5 parameters, your class is doing too much. Constructor injection makes this obvious. Field injection hides it.

Explicit dependencies. Testable code. Simple rule.

Share this article

Written by

Gautam Thapa

Senior Technical Lead working on backend systems where reliability and correctness matter more than novelty.