Skip to content

Why Should I Use Constructor Injection Instead of @Autowired Field Injection in Spring Boot?

Problem

When I write unit tests for my Spring Boot services, I keep running into issues with @Autowired field injection. My tests require the Spring context to load, which makes them slow and hard to debug.

Terminal window
@SpringBootTest
class OrderServiceTest {
@Autowired
private OrderService orderService;
@MockBean
private OrderRepository orderRepository;
}

Every time I run this test, it takes 5-10 seconds just to start the Spring context. And when something goes wrong, the stack trace is buried in Spring internals.

Environment

  • Spring Boot 3.x
  • Java 17+
  • JUnit 5
  • Mockito

What happened?

I used @Autowired on fields because it looked cleaner:

OrderService.java
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private PaymentService paymentService;
@Autowired
private NotificationService notificationService;
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
return orderRepository.save(order);
}
}

This seems fine at first. No constructor boilerplate, just inject and go.

But when I try to write a simple unit test, I hit problems:

OrderServiceTest.java
@SpringBootTest // Heavy context loading
class OrderServiceTest {
@Autowired
private OrderService orderService;
@MockBean
private OrderRepository orderRepository;
@Test
void shouldCreateOrder() {
// Test requires Spring context
// Takes 5+ seconds to run
}
}

I also noticed that my IDE can’t tell me what dependencies OrderService needs without opening the file. And if I forget to add a bean, I only find out at runtime when I call a method that uses it.

How to solve it?

I switched to constructor injection. In Spring Boot 4.3+, I don’t even need @Autowired on the constructor:

OrderService.java
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final NotificationService notificationService;
public OrderService(
OrderRepository orderRepository,
PaymentService paymentService,
NotificationService notificationService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.notificationService = notificationService;
}
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
return orderRepository.save(order);
}
}

Even cleaner with Lombok:

OrderService.java
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final NotificationService notificationService;
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
return orderRepository.save(order);
}
}

Now my unit test is fast and simple:

OrderServiceTest.java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
@Mock
private PaymentService paymentService;
@Mock
private NotificationService notificationService;
@InjectMocks
private OrderService orderService;
@Test
void shouldCreateOrder() {
// Fast unit test, no Spring context
// Runs in milliseconds
OrderRequest request = new OrderRequest("item-123", 2);
Order saved = new Order(request);
when(orderRepository.save(any())).thenReturn(saved);
Order result = orderService.createOrder(request);
assertNotNull(result);
verify(orderRepository).save(any());
}
}

You can see that I succeeded to write a fast, isolated unit test without Spring context.

The reason

I think the key reason constructor injection is better comes down to four things:

1. Explicit dependencies

With field injection, I can’t tell what a class needs without opening the file:

@Service
public class OrderService {
@Autowired private OrderRepository orderRepository;
@Autowired private PaymentService paymentService;
@Autowired private NotificationService notificationService;
@Autowired private AuditService auditService; // Hidden deep in the file
}

With constructor injection, the signature tells me everything:

public OrderService(
OrderRepository orderRepository,
PaymentService paymentService,
NotificationService notificationService,
AuditService auditService) { ... }

2. Easy testing

Field injection forces me to use Spring context or reflection for tests. Constructor injection works with plain Mockito:

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock OrderRepository repo;
@Mock PaymentService payment;
@InjectMocks OrderService service;
}

3. Immutability

Fields with @Autowired can’t be final. Constructor injection lets me make everything immutable:

@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository; // Immutable
private final PaymentService paymentService; // Immutable
}

4. Fail fast

If a dependency is missing:

  • Field injection: Application starts fine. Fails at runtime when you call a method that uses it.
  • Constructor injection: Application fails to start immediately with clear error.
Startup error
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.OrderService
required a bean of type 'com.example.PaymentService'
that could not be found.

Comparison

AspectField InjectionConstructor Injection
TestingRequires Spring contextPlain JUnit + Mockito
ImmutabilityFields mutableCan use final fields
Startup detectionRuntime NPEStartup failure
IDE supportNo hintsClear constructor signature
DocumentationMust read class bodyDependencies in signature

Common mistakes

Mistake 1: Adding @Autowired on constructor (unnecessary since Spring 4.3)

// UNNECESSARY - Spring does this automatically
@Autowired
public OrderService(OrderRepository repo) { ... }

Mistake 2: Not making fields final

@Service
@RequiredArgsConstructor
public class OrderService {
private OrderRepository orderRepository; // Missing final!
// This field won't be included in the generated constructor
}

Mistake 3: Mixing field and constructor injection

Pick one pattern. Constructor injection for required dependencies, setter injection only for optional ones.

Summary

In this post, I showed why constructor injection is preferred over @Autowired field injection in Spring Boot. The key point is that constructor injection makes dependencies explicit, enables fast unit testing without Spring context, ensures immutability with final fields, and catches missing dependencies at startup. With Lombok’s @RequiredArgsConstructor, the pattern is clean and maintainable.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments