Why Field Injection Should Be Avoided in Spring Boot: Benefits of Constructor Injection
This article explains why field injection in Spring Boot should be avoided, highlighting its drawbacks such as poor testability, mutability, tight coupling, risk of null‑pointer exceptions, and circular dependencies, and demonstrates how constructor injection offers a cleaner, more maintainable alternative with code examples.
In the context of Spring Boot dependency injection, there is an ongoing debate about the best practice among field injection, setter injection, and constructor injection.
What is field injection? Field injection involves annotating a private field with @Autowired . Example:
@Component
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order findOrderById(Long id) {
return orderRepository.findById(id);
}
}Why stop using field injection?
1. Testability
Field injection makes unit testing difficult because the dependency is directly injected into a private field, preventing easy mocking outside the Spring context. Example test using reflection:
@RunWith(SpringJUnit4ClassRunner.class)
public class OrderServiceTest {
private OrderService orderService;
@Mock
private OrderRepository orderRepository;
@Before
public void setUp() throws Exception {
orderService = new OrderService();
// This will set the mock orderRepository into orderService's private field
ReflectionTestUtils.setField(orderService, "orderRepository", orderRepository);
}
...
}Using reflection is cumbersome and violates object‑oriented design principles.
2. Immutability
Field injection leaves beans mutable after construction. With constructor injection, dependencies are final and cannot be reassigned, preserving immutability.
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
}
// Constructor injection version
@Component
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}3. Tighter coupling to Spring
Non‑Spring environments cannot use @Autowired directly.
Switching to another DI framework (e.g., Guice) requires refactoring all @Autowired usages.
New developers may be confused by Spring‑specific annotations.
4. Null‑Pointer Exceptions
When a bean is instantiated via its default constructor, injected fields may remain uninitialized, leading to NullPointerException at runtime.
@Component
public class PaymentGateway {
@Autowired
private PaymentQueue paymentQueue;
public void initiate(PaymentRequest request) {
paymentQueue.add(request);
}
}
public class PaymentService {
public void process(PaymentRequest request) {
PaymentGateway gateway = new PaymentGateway();
gateway.initiate(request);
}
}5. Circular dependencies
Field injection can hide circular dependencies, making them harder to detect. Example:
@Service
public class AService {
@Autowired
private BService bService;
}
@Service
public class BService {
@Autowired
private AService aService;
}Constructor injection causes Spring to fail fast with a BeanCurrentlyInCreationException , revealing the issue early.
Conclusion
Although field injection may appear concise, its disadvantages outweigh its convenience. Constructor injection improves testability, enforces immutability, reduces coupling, and aligns with SOLID principles, making Spring Boot applications more maintainable and robust.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.