Spring Framework
Dependency Injection
Field Injection
Spring Autowired annotation
Best Practices

Autowired says field injection not recommended

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When tools warn that field injection is not recommended, they are pointing to maintainability and testability issues, not syntax errors. Field injection works, but it hides required dependencies and makes classes harder to construct safely. Constructor injection is usually the preferred pattern in modern Spring applications.

Why Field Injection Is Discouraged

Field injection with @Autowired on member variables has drawbacks:

  • Dependencies are not visible in constructor signature.
  • Fields cannot be final.
  • Unit tests require reflection or full Spring context.
  • Missing dependency errors appear later in runtime path.

Example of field injection:

java
1@Service
2public class OrderService {
3
4    @Autowired
5    private PaymentGateway paymentGateway;
6
7    public String checkout(String orderId) {
8        return paymentGateway.charge(orderId);
9    }
10}

It works, but dependency visibility is weak.

Preferred Constructor Injection

Constructor injection makes dependencies explicit and immutable.

java
1@Service
2public class OrderService {
3    private final PaymentGateway paymentGateway;
4
5    public OrderService(PaymentGateway paymentGateway) {
6        this.paymentGateway = paymentGateway;
7    }
8
9    public String checkout(String orderId) {
10        return paymentGateway.charge(orderId);
11    }
12}

With one constructor, Spring injects automatically without @Autowired annotation.

Why Constructor Injection Improves Testing

Classes with constructor injection are easy to test directly without Spring container.

java
1class FakeGateway implements PaymentGateway {
2    @Override
3    public String charge(String orderId) {
4        return "ok-" + orderId;
5    }
6}
7
8@Test
9void checkout_returnsGatewayResult() {
10    OrderService service = new OrderService(new FakeGateway());
11    assertEquals("ok-42", service.checkout("42"));
12}

This keeps unit tests fast and isolated.

What About Optional Dependencies

If dependency is optional, use one of these patterns:

  • Constructor parameter as Optional<T>.
  • Provide default implementation bean.
  • Use feature flags with explicit strategy objects.

Avoid nullable hidden fields where absence is discovered too late.

@RequiredArgsConstructor with Lombok

If constructor boilerplate is a concern, Lombok can generate constructor for final fields.

java
1@RequiredArgsConstructor
2@Service
3public class OrderService {
4    private final PaymentGateway paymentGateway;
5}

This keeps explicit dependency design while reducing manual code.

Migrating from Field Injection

Constructor Injection for Optional Dependencies

When dependency is optional, keep constructor injection and pass an ObjectProvider so optional behavior remains explicit.

java
1@Service
2public class NotificationService {
3    private final ObjectProvider<AuditClient> auditClient;
4
5    public NotificationService(ObjectProvider<AuditClient> auditClient) {
6        this.auditClient = auditClient;
7    }
8}

This preserves testability and avoids hidden nullable fields while still supporting optional beans.Incremental migration strategy:

  1. Pick one component at a time.
  2. Move field dependencies to constructor.
  3. Mark dependencies final.
  4. Update tests to direct instantiation.

This approach avoids large risky refactors and improves code quality steadily.

When Field Injection Might Still Appear

Legacy codebases and quick prototypes may still use field injection. If immediate migration is not possible, at least document dependencies and plan conversion in maintenance backlog.

For production-critical modules, prioritize constructor migration first because those components benefit most from strong dependency clarity.

Team-Wide Convention Enforcement

To prevent regression into field injection, add static-analysis checks in CI and fail pull requests that introduce new field-level @Autowired usage in application modules. This keeps dependency style consistent and reinforces constructor-first architecture over time.

If full enforcement is too disruptive initially, run in warning mode and migrate gradually by module.## Common Pitfalls

  • Treating warning as stylistic only and ignoring design impact.
  • Keeping hidden dependency graphs across large services.
  • Mixing field and constructor injection in same class.
  • Assuming full integration tests replace need for unit-testable design.
  • Delaying migration until dependency problems become runtime incidents.

Summary

  • Field injection works but reduces visibility and testability.
  • Constructor injection is the recommended default in Spring.
  • Explicit dependencies improve immutability and error detection.
  • Tests become simpler when services can be instantiated directly.
  • Migrate legacy field injection gradually with clear priorities.

Course illustration
Course illustration

All Rights Reserved.