Spring Boot’s Dependency Injection is fundamental to building maintainable, testable, and loosely coupled applications. Among the various injection methods available, constructor injection has emerged as the recommended approach over field injection with @Autowired. This choice significantly impacts the maintainability, testability, and robustness of your code.
Understanding Dependency Injection in Spring Boot
Dependency Injection is a design pattern where objects receive their dependencies rather than creating them internally. This pattern forms the backbone of Spring’s Inversion of Control container, enabling looser coupling between components and facilitating easier testing.
Spring Boot provides three main approaches to dependency injection: constructor injection, setter injection, and field injection. Each method has distinct characteristics and use cases, but recent developments in Spring have shifted the recommended practices significantly toward constructor injection.
Three Main Approaches to Dependency Injection
Before diving into approaches, let’s establish context. @Autowired is an annotation provided by Spring that enables automatic injection of dependencies into Spring-managed beans. With a single annotation, classes will be connected automatically. This annotation can be applied in three primary ways:
| Injection Type | Pros | Cons | Best For |
| Field | Simple, concise code | Not immutable, hard to test, hides dependencies | Test classes, prototypes |
| Setter | Allows optional dependencies, can change during lifecycle | Not immutable, potential null values | Rare cases with optional or changing dependencies |
| Constructor | Immutable, explicit dependencies, easy testing | Slightly more verbose | Most classes |
Field Injection
The simplest form of injection involves annotating class fields directly with @Autowired. It is commonly found in many Spring codebases due to its brevity and simplicity.
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
// Service methods
}
Setter Injection
Another approach uses @Autowired on setter methods, which allows for optional dependencies and can modify dependencies after instantiation.
@Service
public class UserService {
private UserRepository userRepository;
private EmailService emailService;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
// Service methods
}
Constructor Injection
Constructor Injection involves providing dependencies through a class constructor.
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// Service methods
}
With modern versions of Spring, the @Autowired annotation isn’t required for constructor injection as Spring automatically detects and injects dependencies through constructors. This method ensures dependencies are available from the moment an object is instantiated.
Possible Use Case for Field Injection
Creating Tests
One thing I’ve seen field injection being used in Spring is when creating tests. In tests, we typically care less about constructor immutability, and are more focused on easily accessing the components we need to test.
@SpringBootTest
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@MockBean
private EmailService emailService;
@Test
void registerUserShouldSaveUser_WhenUserIsValid() {
// Given
User newUser = new User("testuser", "[email protected]");
when(emailService.sendWelcomeEmail(any(User.class))).thenReturn(true);
// When
userService.registerUser(newUser);
// Then
User savedUser = userRepository.findByUsername("testuser");
assertNotNull(savedUser);
assertEquals("[email protected]", savedUser.getEmail());
verify(emailService).sendWelcomeEmail(any(User.class));
}
}
Configuration Classes
For classes with many optional dependencies or complex configuration logic, field injection might be appropriate:
@Configuration
public class SecurityConfig {
@Autowired(required = false)
private SSLConfigurer sslConfigurer;
@Autowired(required = false)
private CustomAuthenticationProvider authProvider;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// Use optional dependencies if available
if (sslConfigurer != null) {
sslConfigurer.configure(http);
}
return http.build();
}
}
Why Constructor Injection is Preferred
Immutability and Null Safety
It allows dependencies to be declared as final fields, ensuring they cannot be changed after initialization and will never be null during the bean’s lifecycle. This provides greater robustness in your application.
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public UserService(UserRepository userRepository, EmailService emailService){
this.userRepository = userRepository;
this.emailService = emailService;
}
// Service methods
}
Simplified Testing
It allows dependencies to be mocked or stubbed directly in test constructors without requiring Spring context or reflection based tools.
@Test
public void registerUserShouldCompleteSuccessfully_WhenUserDataIsValid() {
// Mock dependencies
UserRepository mockRepository = mock(UserRepository.class);
EmailService mockEmailService = mock(EmailService.class);
// Create service with mocked dependencies
UserService userService = new UserService(mockRepository, mockEmailService);
// Test the service
userService.registerUser(new User());
}
Official Spring Recommendation
The Spring team now officially recommends constructor injection as the preferred approach. Since Spring Framework 4.3, classes with a single constructor don’t even require the @Autowired annotation, as Spring will automatically use that constructor for dependency injection.
Summary
As things go, it’s always about the context of the environment that we’re working on. When evaluating dependency injection approaches in Spring Boot:
- Constructor injection offers immutability, explicit dependencies, and easier testing, making it the preferred choice for most components
- Field injection remains useful for test classes and configurations with many optional dependencies
- Setter injection can be appropriate when dependencies may change during the bean’s lifecycle
It’s important to know the tradeoffs of one approach versus another and choose what would benefit your codebase the most. For new Spring Boot applications, following the Spring team’s recommendation to prefer constructor injection will generally lead to more robust, maintainable, and testable code.
References
https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html
https://www.linkedin.com/pulse/getting-started-dependency-injection-spring-boot-mohamed-ezzat-iipaf










