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.
@SpringBootTestclass 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:
@Servicepublic 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:
@SpringBootTest // Heavy context loadingclass 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:
@Servicepublic 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:
@Service@RequiredArgsConstructorpublic 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:
@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:
@Servicepublic 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@RequiredArgsConstructorpublic 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.
***************************APPLICATION FAILED TO START***************************
Description:
Parameter 0 of constructor in com.example.OrderServicerequired a bean of type 'com.example.PaymentService'that could not be found.Comparison
| Aspect | Field Injection | Constructor Injection |
|---|---|---|
| Testing | Requires Spring context | Plain JUnit + Mockito |
| Immutability | Fields mutable | Can use final fields |
| Startup detection | Runtime NPE | Startup failure |
| IDE support | No hints | Clear constructor signature |
| Documentation | Must read class body | Dependencies in signature |
Common mistakes
Mistake 1: Adding @Autowired on constructor (unnecessary since Spring 4.3)
// UNNECESSARY - Spring does this automatically@Autowiredpublic OrderService(OrderRepository repo) { ... }Mistake 2: Not making fields final
@Service@RequiredArgsConstructorpublic 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