
Spring Boot Dependency Injection
- 1.7k installs
- 311 repo stars
- Updated June 22, 2026
- giuseppe-trisciuoglio/developer-kit
Constructor-first dependency injection patterns with explicit handling of mandatory and optional collaborators, validated via minimal context tests.
About
This skill teaches constructor-first dependency injection patterns for Spring Boot applications, covering mandatory collaborators via constructors, optional dependencies via ObjectProvider or no-op implementations, bean selection with @Primary and @Qualifier, and validation through minimal context tests. Developers use it when building new @Service, @Component, @Repository, or @Configuration classes, refactoring legacy field injection code, resolving ambiguous bean wiring, or troubleshooting Spring context startup failures. Key workflows include separating mandatory from optional dependencies, keeping wiring logic in @Configuration classes, and testing services without Spring containers before integration testing.
- Constructor injection for mandatory dependencies with final fields; no @Autowired needed for single constructors
- ObjectProvider<T> for lazy optional dependencies; no-op implementations and @ConditionalOnProperty for feature flags
- @Primary for default beans; @Qualifier for named variants; keep selection rules in configuration classes
- Minimal context tests to validate bean loading before full @SpringBootTest; unit tests without Spring container
- Avoid field injection, nullable collaborators, and circular dependencies; keep framework logic out of business code
Spring Boot Dependency Injection by the numbers
- 1,673 all-time installs (skills.sh)
- +55 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #278 of 4,386 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
spring-boot-dependency-injection capabilities & compatibility
- Capabilities
- design constructor first dependency injection pa · resolve multiple beans with @primary and @qualif · handle optional dependencies with objectprovider · validate spring wiring with minimal context test · refactor field injection to constructor injectio
- Use cases
- api development · testing · refactoring
- Platforms
- macOS · Windows · Linux
- Runs
- Runs locally
What spring-boot-dependency-injection says it does
Provides constructor-first dependency injection patterns for Spring Boot
Mandatory collaborators belong in the constructor.
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill spring-boot-dependency-injectionAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.7k |
|---|---|
| repo stars | ★ 311 |
| Security audit | 3 / 3 scanners passed |
| Last updated | June 22, 2026 |
| Repository | giuseppe-trisciuoglio/developer-kit ↗ |
What it does
Design and validate Spring Boot dependency injection patterns for services and configurations using constructor-first wiring.
Who is it for?
Building new Spring Boot services; refactoring legacy field injection; resolving bean selection conflicts; designing for testability.
Skip if: Simple scripts or one-off utilities; frameworks without Spring dependency injection.
When should I use this skill?
Creating @Service, @Component, @Repository, or @Configuration classes; troubleshooting Spring context failures; removing field injection.
What you get
Services and configurations with clear, testable dependency graphs; reduced Spring startup failures and improved unit test isolation.
- Constructor-injected service or configuration class
- Wiring validation test
- Documented bean selection strategy
By the numbers
- 6 core instructions covering separation, defaults, optional handling, selection, configuration, and validation
- 3 complete examples: constructor-first service, optional dependency with no-op, multiple beans with @Primary/@Qualifier
Files
Spring Boot Dependency Injection
Overview
Provides constructor-first dependency injection patterns for Spring Boot:
- mandatory collaborators via constructor injection
- optional collaborators via
ObjectProvideror no-op fallbacks - bean selection via
@Primaryand@Qualifier - validation via minimal context tests before full integration
When to Use
Use this skill when:
- creating a new
@Service,@Component,@Repository, or@Configurationclass - replacing field injection in legacy Spring code
- resolving multiple beans of the same type with qualifiers or primary beans
- handling optional features, adapters, or integrations without null-driven wiring
- reviewing circular dependencies or brittle context startup failures
- preparing code for direct constructor-based unit testing
Instructions
1. Separate mandatory and optional collaborators
For each class, identify:
- mandatory collaborators required for correct behavior
- optional collaborators that enable integrations, caching, notifications, or feature-flagged behavior
Mandatory collaborators belong in the constructor. Optional ones need an explicit strategy such as ObjectProvider, conditional beans, or a no-op implementation.
2. Default to constructor injection
For application services and adapters:
- inject mandatory dependencies through the constructor
- keep injected fields
final - instantiate the class directly in unit tests without starting Spring
A single constructor is usually enough; @Autowired is unnecessary in that case.
3. Resolve optional behavior intentionally
Good options include:
ObjectProvider<T>when lazy access is useful@ConditionalOnPropertyor@ConditionalOnMissingBeanwhen wiring should change by configuration- a no-op implementation when the caller should not care whether the feature is enabled
Avoid nullable collaborators that leave runtime behavior ambiguous.
4. Use bean selection annotations only when needed
When multiple beans share the same type:
- use
@Primaryfor the default implementation - use
@Qualifierfor named variants - keep the qualifier names stable and easy to grep
If selection rules become complex, move them into a dedicated configuration class instead of spreading them across services.
5. Keep wiring in configuration, not business code
Use @Configuration and @Bean methods when:
- the object comes from a third-party library
- conditional creation logic is needed
- you need environment-specific wiring or explicit composition
Business services should not know how infrastructure collaborators are instantiated.
6. Validate wiring explicitly
After writing a new service or configuration:
1. Verify the bean loads with a minimal context test:
@SpringBootTest
@ContextConfiguration(classes = UserService.class)
class UserServiceWiringTest {
@Autowired UserService userService;
@Test void serviceIsInstantiated() { assertNotNull(userService); }
}2. Run constructor-based unit tests for service behavior (no Spring needed). 3. Add slice tests only when MVC, JPA, or messaging integration must be verified. 4. Reserve `@SpringBootTest` for container-wide wiring validation.
Failures at step 1 indicate wiring issues before business logic is added.
Examples
Example 1: Constructor-first application service
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailSender emailSender;
public UserService(UserRepository userRepository, EmailSender emailSender) {
this.userRepository = userRepository;
this.emailSender = emailSender;
}
public User register(UserRegistrationRequest request) {
User user = userRepository.save(User.from(request));
emailSender.sendWelcome(user);
return user;
}
}This class is easy to instantiate directly in a unit test with mocks.
Example 2: Optional dependency with a no-op fallback
@Service
public class ReportService {
private final ReportRepository reportRepository;
private final NotificationGateway notificationGateway;
public ReportService(
ReportRepository reportRepository,
ObjectProvider<NotificationGateway> notificationGatewayProvider
) {
this.reportRepository = reportRepository;
this.notificationGateway = notificationGatewayProvider.getIfAvailable(NotificationGateway::noOp);
}
}This keeps optional behavior explicit without leaking null handling through the rest of the class.
Example 3: Multiple beans with clear selection
@Configuration
public class PaymentConfiguration {
@Bean
@Primary
PaymentGateway stripeGateway() {
return new StripePaymentGateway();
}
@Bean
@Qualifier("fallbackGateway")
PaymentGateway mockGateway() {
return new MockPaymentGateway();
}
}Use @Primary for the default path and @Qualifier only where a specific variant is required.
Best Practices
- Prefer constructor injection for mandatory dependencies.
- Keep service constructors small; if a class needs too many collaborators, the design probably wants another abstraction.
- Use no-op or conditional beans instead of nullable optional dependencies.
- Keep framework-specific creation logic in configuration classes.
- Test services without Spring first, then add container tests only where they add value.
- Remove field injection during refactors instead of extending it.
Constraints and Warnings
- Field injection hides dependencies and makes tests harder to write.
- Circular dependencies are usually a design problem, not a wiring trick to solve with
@Lazy. - Overusing qualifiers can make the codebase hard to reason about; prefer better abstractions or clearer configuration.
- Optional collaborators still need deterministic behavior when absent.
- Full-context tests can hide the real source of wiring failures if used too early.
References
references/reference.mdreferences/examples.mdreferences/spring-official-dependency-injection.md
Related Skills
spring-boot-crud-patternsspring-boot-rest-api-standardsunit-test-service-layer
Spring Boot Dependency Injection - Examples
Comprehensive examples demonstrating dependency injection patterns, from basic to advanced scenarios.
Example 1: Constructor Injection (Recommended)
The preferred pattern for mandatory dependencies.
// With Lombok @RequiredArgsConstructor (RECOMMENDED)
@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final PasswordEncoder passwordEncoder;
public User registerUser(CreateUserRequest request) {
log.info("Registering user: {}", request.getEmail());
User user = User.builder()
.email(request.getEmail())
.name(request.getName())
.password(passwordEncoder.encode(request.getPassword()))
.build();
User saved = userRepository.save(user);
emailService.sendWelcomeEmail(saved.getEmail());
return saved;
}
}
// Without Lombok (Explicit)
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository,
EmailService emailService,
PasswordEncoder passwordEncoder) {
this.userRepository = Objects.requireNonNull(userRepository);
this.emailService = Objects.requireNonNull(emailService);
this.passwordEncoder = Objects.requireNonNull(passwordEncoder);
}
public User registerUser(CreateUserRequest request) {
// Implementation
}
}Test (Easy - No Spring Needed)
@Test
void shouldRegisterUserAndSendEmail() {
// Arrange - Create mocks manually
UserRepository mockRepository = mock(UserRepository.class);
EmailService mockEmailService = mock(EmailService.class);
PasswordEncoder mockEncoder = mock(PasswordEncoder.class);
UserService service = new UserService(mockRepository, mockEmailService, mockEncoder);
User user = User.builder().email("test@example.com").build();
when(mockRepository.save(any())).thenReturn(user);
when(mockEncoder.encode("password")).thenReturn("encoded");
// Act
User result = service.registerUser(new CreateUserRequest("test@example.com", "Test", "password"));
// Assert
assertThat(result.getEmail()).isEqualTo("test@example.com");
verify(mockEmailService).sendWelcomeEmail("test@example.com");
}---
Example 2: Setter Injection for Optional Dependencies
Use setter injection ONLY for optional dependencies with sensible defaults.
@Service
public class ReportService {
private final ReportRepository reportRepository;
private EmailService emailService; // Optional
private CacheService cacheService; // Optional
// Constructor for mandatory dependency
public ReportService(ReportRepository reportRepository) {
this.reportRepository = Objects.requireNonNull(reportRepository);
}
// Setters for optional dependencies
@Autowired(required = false)
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
@Autowired(required = false)
public void setCacheService(CacheService cacheService) {
this.cacheService = cacheService;
}
public Report generateReport(ReportRequest request) {
Report report = reportRepository.create(request.getTitle());
// Use optional services if available
if (emailService != null) {
emailService.sendReport(report);
}
if (cacheService != null) {
cacheService.cache(report);
}
return report;
}
}---
Example 3: Configuration with Multiple Bean Definitions
@Configuration
public class AppConfig {
// Bean 1: Database
@Bean
public DataSource dataSource(
@Value("${spring.datasource.url}") String url,
@Value("${spring.datasource.username}") String username,
@Value("${spring.datasource.password}") String password) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(20);
return new HikariDataSource(config);
}
// Bean 2: Transaction Manager (depends on DataSource)
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
// Bean 3: Repository (depends on DataSource via JPA)
@Bean
public UserRepository userRepository(UserJpaRepository jpaRepository) {
return new UserRepositoryAdapter(jpaRepository);
}
// Bean 4: Service (depends on Repository)
@Bean
public UserService userService(UserRepository repository) {
return new UserService(repository);
}
}---
Example 4: Resolving Ambiguities with @Qualifier
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDB")
public DataSource primaryDataSource() {
return new HikariDataSource();
}
@Bean(name = "secondaryDB")
public DataSource secondaryDataSource() {
return new HikariDataSource();
}
}
@Service
public class MultiDatabaseService {
private final DataSource primaryDataSource;
private final DataSource secondaryDataSource;
// Using @Qualifier to resolve ambiguity
public MultiDatabaseService(
@Qualifier("primaryDB") DataSource primary,
@Qualifier("secondaryDB") DataSource secondary) {
this.primaryDataSource = primary;
this.secondaryDataSource = secondary;
}
public void performOperation() {
// Use primary for writes
executeUpdate(primaryDataSource);
// Use secondary for reads
executeQuery(secondaryDataSource);
}
}
// Alternative: Using @Primary
@Configuration
public class PrimaryDataSourceConfig {
@Bean
@Primary // This bean is preferred when multiple exist
public DataSource primaryDataSource() {
return new HikariDataSource();
}
@Bean
public DataSource secondaryDataSource() {
return new HikariDataSource();
}
}---
Example 5: Conditional Bean Registration
@Configuration
public class OptionalFeatureConfig {
// Only create if feature is enabled
@Bean
@ConditionalOnProperty(name = "feature.notifications.enabled", havingValue = "true")
public NotificationService notificationService() {
return new EmailNotificationService();
}
// Fallback if no other bean exists
@Bean
@ConditionalOnMissingBean(NotificationService.class)
public NotificationService defaultNotificationService() {
return new NoOpNotificationService();
}
// Only create if class is on classpath
@Bean
@ConditionalOnClass(RedisTemplate.class)
public CacheService cacheService() {
return new RedisCacheService();
}
}
@Service
public class OrderService {
private final NotificationService notificationService;
public OrderService(NotificationService notificationService) {
this.notificationService = notificationService; // Works regardless of implementation
}
public void createOrder(Order order) {
// Always works, but behavior depends on enabled features
notificationService.sendConfirmation(order);
}
}---
Example 6: Profiles and Environment-Specific Configuration
@Configuration
@Profile("production")
public class ProductionConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://prod-db:5432/production");
config.setMaximumPoolSize(30);
config.setMaxLifetime(1800000); // 30 minutes
return new HikariDataSource(config);
}
@Bean
public SecurityService securityService() {
return new StrictSecurityService();
}
}
@Configuration
@Profile("test")
public class TestConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
@Bean
public SecurityService securityService() {
return new PermissiveSecurityService();
}
}
@Configuration
@Profile("development")
public class DevelopmentConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/dev");
return new HikariDataSource(config);
}
@Bean
public SecurityService securityService() {
return new DebugSecurityService();
}
}Usage:
export SPRING_PROFILES_ACTIVE=production
# or in application.properties:
# spring.profiles.active=production---
Example 7: Lazy Initialization
@Configuration
public class ExpensiveResourceConfig {
@Bean
@Lazy // Created only when first accessed
public ExpensiveService expensiveService() {
System.out.println("ExpensiveService initialized (lazy)");
return new ExpensiveService();
}
@Bean
public NormalService normalService(ExpensiveService expensive) {
// ExpensiveService not created yet
return new NormalService(expensive); // Lazy proxy injected here
}
}
@SpringBootTest
class LazyInitializationTest {
@Test
void shouldInitializeExpensiveServiceLazy() {
ApplicationContext context = new AnnotationConfigApplicationContext(ExpensiveResourceConfig.class);
// ExpensiveService not initialized yet
assertThat(context.getBean(NormalService.class)).isNotNull();
// Now ExpensiveService is initialized
ExpensiveService service = context.getBean(ExpensiveService.class);
assertThat(service).isNotNull();
}
}---
Example 8: Circular Dependency Resolution with Events
// ❌ BAD - Circular dependency
@Service
public class UserService {
private final OrderService orderService;
public UserService(OrderService orderService) {
this.orderService = orderService; // Circular!
}
}
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService; // Circular!
}
}
// ✅ GOOD - Use events to decouple
public class UserRegisteredEvent extends ApplicationEvent {
private final String userId;
public UserRegisteredEvent(Object source, String userId) {
super(source);
this.userId = userId;
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;
public User registerUser(CreateUserRequest request) {
User user = userRepository.save(User.create(request));
eventPublisher.publishEvent(new UserRegisteredEvent(this, user.getId()));
return user;
}
}
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
// Create welcome order when user registers
Order welcomeOrder = Order.createWelcomeOrder(event.getUserId());
orderRepository.save(welcomeOrder);
}
}---
Example 9: Component Scanning
@Configuration
@ComponentScan(basePackages = {
"com.example.users",
"com.example.products",
"com.example.orders"
})
public class AppConfig {
}
// Alternative: Exclude packages
@Configuration
@ComponentScan(basePackages = "com.example",
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX,
pattern = "com\\.example\\.internal\\..*"))
public class AppConfig {
}
// Auto-discovered by Spring Boot
@SpringBootApplication // Implies @ComponentScan("package.of.main.class")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}---
Example 10: Testing with Constructor Injection
// ❌ Service with field injection (hard to test)
@Service
public class BadUserService {
@Autowired
private UserRepository userRepository;
public User getUser(Long id) {
return userRepository.findById(id).orElse(null);
}
}
@Test
void testBadService() {
// Must use Spring to test this
UserService service = new BadUserService();
// Can't inject mocks without reflection or Spring
}
// ✅ Service with constructor injection (easy to test)
@Service
@RequiredArgsConstructor
public class GoodUserService {
private final UserRepository userRepository;
public User getUser(Long id) {
return userRepository.findById(id).orElse(null);
}
}
@Test
void testGoodService() {
// Can test directly without Spring
UserRepository mockRepository = mock(UserRepository.class);
UserService service = new GoodUserService(mockRepository);
User mockUser = new User(1L, "Test");
when(mockRepository.findById(1L)).thenReturn(Optional.of(mockUser));
User result = service.getUser(1L);
assertThat(result.getName()).isEqualTo("Test");
}
// Integration test
@SpringBootTest
@ActiveProfiles("test")
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
void shouldFetchUserFromDatabase() {
User user = User.create("test@example.com");
userRepository.save(user);
User retrieved = userService.getUser(user.getId());
assertThat(retrieved.getEmail()).isEqualTo("test@example.com");
}
}These examples cover constructor injection (recommended), setter injection (optional dependencies), configuration, testing patterns, and common best practices for dependency injection in Spring Boot.
Spring Boot Dependency Injection - References
Complete API reference for dependency injection in Spring Boot applications.
Core Interfaces and Classes
ApplicationContext
Root interface for Spring IoC container.
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory,
HierarchicalBeanFactory, MessageSource,
ApplicationEventPublisher, ResourcePatternResolver {
// Get a bean by type
<T> T getBean(Class<T> requiredType);
// Get a bean by name and type
<T> T getBean(String name, Class<T> requiredType);
// Get all beans of a type
<T> Map<String, T> getBeansOfType(Class<T> type);
// Get all bean names
String[] getBeanDefinitionNames();
}BeanFactory
Lower-level interface for accessing beans (used internally).
public interface BeanFactory {
Object getBean(String name);
<T> T getBean(String name, Class<T> requiredType);
<T> T getBean(Class<T> requiredType);
Object getBean(String name, Object... args);
}Dependency Injection Annotations
@Autowired
Auto-wire dependencies (property, constructor, or method injection).
@Autowired // Required dependency
@Autowired(required = false) // Optional dependency
@Autowired private UserRepository repository; // Field injection (avoid)@Qualifier
Disambiguate when multiple beans of same type exist.
@Autowired
@Qualifier("primaryDB")
private DataSource dataSource;
@Bean
@Qualifier("cache")
public CacheService cacheService() { }@Primary
Mark bean as preferred when multiple exist.
@Bean
@Primary
public DataSource primaryDataSource() { }
@Bean
public DataSource secondaryDataSource() { }@Value
Inject properties and SpEL expressions.
@Value("${app.name}") // Property injection
@Value("${app.port:8080}") // With default value
@Value("#{T(java.lang.Math).PI}") // SpEL expression
@Value("#{'${app.servers}'.split(',')}") // Collection
private String value;@Lazy
Delay bean initialization until first access.
@Bean
@Lazy
public ExpensiveBean expensiveBean() { }
@Autowired
@Lazy
private ExpensiveBean bean; // Lazy proxy@Scope
Define bean lifecycle scope.
@Scope("singleton") // One per container (default)
@Scope("prototype") // New instance each time
@Scope("request") // One per HTTP request
@Scope("session") // One per HTTP session
@Scope("application") // One per ServletContext
@Scope("websocket") // One per WebSocket session@Configuration
Mark class as providing bean definitions.
@Configuration
public class AppConfig {
@Bean
public UserService userService() { }
}@Bean
Define a bean in configuration class.
@Bean
public UserService userService(UserRepository repository) {
return new UserService(repository);
}
@Bean(name = "customName")
public UserService userService() { }
@Bean(initMethod = "init", destroyMethod = "cleanup")
public UserService userService() { }@Component / @Service / @Repository / @Controller
Stereotype annotations for component scanning.
@Component // Generic Spring component
@Service // Business logic layer
@Repository // Data access layer
@Controller // Web layer (MVC)
@RestController // Web layer (REST)
public class UserService { }Conditional Bean Registration
@ConditionalOnProperty
Create bean only if property exists.
@Bean
@ConditionalOnProperty(
name = "feature.notifications.enabled",
havingValue = "true"
)
public NotificationService notificationService() { }
// OR if property matches any value
@ConditionalOnProperty(name = "feature.enabled")
public NotificationService notificationService() { }@ConditionalOnClass / @ConditionalOnMissingClass
Create bean based on classpath availability.
@Bean
@ConditionalOnClass(RedisTemplate.class)
public CacheService cacheService() { }
@Bean
@ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")
public LocalCacheService fallbackCacheService() { }@ConditionalOnBean / @ConditionalOnMissingBean
Create bean based on other beans.
@Bean
@ConditionalOnBean(DataSource.class)
public UserService userService() { }
@Bean
@ConditionalOnMissingBean
public UserService defaultUserService() { }@ConditionalOnExpression
Create bean based on SpEL expression.
@Bean
@ConditionalOnExpression("'${environment}'.equals('production')")
public SecurityService securityService() { }Profile-Based Configuration
@Profile
Activate bean only in specific profiles.
@Configuration
@Profile("production")
public class ProductionConfig { }
@Bean
@Profile({"dev", "test"})
public TestDataLoader testDataLoader() { }
@Bean
@Profile("!production") // All profiles except production
public DebugService debugService() { }Activate profiles:
# application.properties
spring.profiles.active=production
# application-production.properties
# Profile-specific property file
spring.datasource.url=jdbc:postgresql://prod-db:5432/prodComponent Scanning
@ComponentScan
Configure component scanning.
@Configuration
@ComponentScan(basePackages = {"com.example.users", "com.example.products"})
public class AppConfig { }
@Configuration
@ComponentScan(
basePackages = "com.example",
excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "com\\.example\\.internal\\..*"
)
)
public class AppConfig { }Filter Types
FilterType.ANNOTATION- By annotationFilterType.ASSIGNABLE_TYPE- By class typeFilterType.ASPECTJ- By AspectJ patternFilterType.REGEX- By regex patternFilterType.CUSTOM- Custom filter
Injection Points
Constructor Injection (Recommended)
@Service
@RequiredArgsConstructor // Lombok generates constructor
public class UserService {
private final UserRepository repository; // Final field
private final EmailService emailService;
}
// Explicit
@Service
public class UserService {
private final UserRepository repository;
public UserService(UserRepository repository) {
this.repository = Objects.requireNonNull(repository);
}
}Setter Injection (Optional Dependencies Only)
@Service
public class UserService {
private final UserRepository repository;
private EmailService emailService; // Optional
public UserService(UserRepository repository) {
this.repository = repository;
}
@Autowired(required = false)
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}Field Injection (❌ Avoid)
// ❌ NOT RECOMMENDED
@Service
public class UserService {
@Autowired
private UserRepository repository; // Hidden dependency
@Autowired
private EmailService emailService; // Mutable state
}Circular Dependency Resolution
Problem: Circular Dependencies
// ❌ WILL FAIL
@Service
public class UserService {
private final OrderService orderService;
public UserService(OrderService orderService) {
this.orderService = orderService; // Circular!
}
}
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService; // Circular!
}
}Solution 1: Setter Injection
@Service
public class UserService {
private final UserRepository userRepository;
private OrderService orderService; // Optional
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired(required = false)
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
}Solution 2: Event-Driven (Recommended)
public class UserRegisteredEvent extends ApplicationEvent {
private final String userId;
public UserRegisteredEvent(Object source, String userId) {
super(source);
this.userId = userId;
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;
public User registerUser(CreateUserRequest request) {
User user = userRepository.save(User.create(request));
eventPublisher.publishEvent(new UserRegisteredEvent(this, user.getId()));
return user;
}
}
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
orderRepository.createWelcomeOrder(event.getUserId());
}
}Solution 3: Refactor to Separate Concerns
// Shared service without circular dependency
@Service
@RequiredArgsConstructor
public class UserOrderService {
private final UserRepository userRepository;
private final OrderRepository orderRepository;
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserOrderService userOrderService;
}
@Service
@RequiredArgsConstructor
public class OrderService {
private final UserOrderService userOrderService;
}ObjectProvider for Flexibility
ObjectProvider Interface
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
T getObject();
T getObject(Object... args);
T getIfAvailable();
T getIfAvailable(Supplier<T> defaultSupplier);
void ifAvailable(Consumer<T> consumer);
void ifAvailableOrElse(Consumer<T> consumer, Runnable emptyRunnable);
<X> ObjectProvider<X> map(Function<? super T, ? extends X> mapper);
<X> ObjectProvider<X> flatMap(Function<? super T, ObjectProvider<X>> mapper);
Optional<T> getIfUnique();
Optional<T> getIfUnique(Supplier<T> defaultSupplier);
}Usage Example
@Service
public class FlexibleService {
private final ObjectProvider<CacheService> cacheProvider;
public FlexibleService(ObjectProvider<CacheService> cacheProvider) {
this.cacheProvider = cacheProvider;
}
public void process() {
// Safely handle optional bean
cacheProvider.ifAvailable(cache -> cache.invalidate());
// Get with fallback
CacheService cache = cacheProvider.getIfAvailable(() -> new NoOpCache());
// Iterate if multiple beans exist
cacheProvider.forEach(cache -> cache.initialize());
}
}Bean Lifecycle Hooks
InitializingBean / DisposableBean
@Component
public class ResourceManager implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
// Called after constructor and property injection
System.out.println("Bean initialized");
}
@Override
public void destroy() throws Exception {
// Called when context shutdown
System.out.println("Bean destroyed");
}
}@PostConstruct / @PreDestroy
@Component
public class ResourceManager {
@PostConstruct
public void init() {
// Called after constructor and injection
System.out.println("Bean initialized");
}
@PreDestroy
public void cleanup() {
// Called before bean destroyed
System.out.println("Bean destroyed");
}
}@Bean with initMethod and destroyMethod
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup")
public ResourceManager resourceManager() {
return new ResourceManager();
}
}
public class ResourceManager {
public void init() {
System.out.println("Initialized");
}
public void cleanup() {
System.out.println("Cleaned up");
}
}Testing Patterns
Unit Test (No Spring)
class UserServiceTest {
private UserRepository mockRepository;
private UserService service;
@BeforeEach
void setUp() {
mockRepository = mock(UserRepository.class);
service = new UserService(mockRepository); // Manual injection
}
@Test
void shouldFetchUser() {
User user = new User(1L, "Test");
when(mockRepository.findById(1L)).thenReturn(Optional.of(user));
User result = service.getUser(1L);
assertThat(result).isEqualTo(user);
}
}Integration Test (With Spring)
@SpringBootTest
@ActiveProfiles("test")
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
void shouldFetchUserFromDatabase() {
User user = User.create("test@example.com");
userRepository.save(user);
User retrieved = userService.getUser(user.getId());
assertThat(retrieved.getEmail()).isEqualTo("test@example.com");
}
}Slice Test
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean // Mock the service
private UserService userService;
@Test
void shouldReturnUser() throws Exception {
User user = new User(1L, "Test");
when(userService.getUser(1L)).thenReturn(user);
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk());
}
}Best Practices Summary
| Practice | Recommendation | Why |
|---|---|---|
| Constructor injection | ✅ Mandatory | Explicit, immutable, testable |
| Setter injection | ⚠️ Optional deps | Clear optionality |
| Field injection | ❌ Never | Hidden, untestable |
@Autowired on constructor | ✅ Implicit (4.3+) | Clear intent |
Lombok @RequiredArgsConstructor | ✅ Recommended | Reduces boilerplate |
| Circular dependencies | ❌ Avoid | Use events instead |
| Too many dependencies | ❌ Avoid | SRP violation |
@Lazy for expensive beans | ✅ Appropriate | Faster startup |
| Profiles for environments | ✅ Recommended | Environment-specific config |
@Value for properties | ✅ Recommended | Type-safe injection |
External Resources
Official Documentation
Related Skills
- spring-boot-crud-patterns/SKILL.md - DI in CRUD applications
- spring-boot-test-patterns/SKILL.md - Testing with DI
- spring-boot-rest-api-standards/SKILL.md - REST layer with DI
Books
- "Spring in Action" (latest edition)
- "Spring Microservices in Action"
Articles
Spring Framework Official Guidance: Dependency Injection (Clean Excerpt)
Source: https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html (retrieved via u2m -v on current date).
Key Highlights
- Emphasize constructor-based dependency injection to make collaborators explicit and enable immutable design.
- Use setter injection only for optional dependencies or when a dependency can change after initialization.
- Field injection is supported but discouraged because it hides dependencies and complicates testing.
- The IoC container resolves constructor arguments by type, name, and order; prefer unique types or qualify arguments with
@Qualifieror XML attributes when ambiguity exists. - Static factory methods behave like constructors for dependency injection and can receive collaborators through arguments.
Constructor-Based DI
public class SimpleMovieLister {
private final MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}- The container selects the matching constructor and provides dependencies by type.
- When argument types are ambiguous, specify indexes (
@ConstructorProperties, XMLindexattribute) or qualifiers.
Setter-Based DI
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}- Invoke only when a collaborator is optional or changeable.
- Use
@Autowired(required = false)orObjectProvider<T>to guard optional collaborators.
Reference Snippets
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="exampleBean" class="examples.ExampleBean">
<property name="beanOne" ref="anotherExampleBean"/>
<property name="beanTwo" ref="yetAnotherBean"/>
</bean>- Spring treats constructor-arg entries as positional parameters unless
indexortypeis provided. - Setter injection uses
<property>elements mapped by name.
Additional Notes
- Combine configuration classes with
@Importto wire dependencies declared in different modules. - Lazy initialization (
@Lazy) delays bean creation but defers error detection; prefer eager initialization unless startup time is critical. - Profiles (
@Profile) activate different wiring scenarios per environment (for example,@Profile("test")). - Testing support allows constructor injection in production code while wiring mocks manually (no container required) or relying on the TestContext framework for integration tests.
Related skills
How it compares
Choose Spring Boot Dependency Injection over generic Java skills when you need Spring-specific @Service wiring and Lombok constructor injection patterns.
FAQ
When should I use ObjectProvider instead of @Autowired?
Use ObjectProvider<T> for optional dependencies where lazy access or conditional presence is useful; it avoids null and makes intent explicit.
What is the difference between @Primary and @Qualifier?
@Primary designates the default bean when no qualifier is specified; @Qualifier names a specific variant for explicit selection.
How do I test a service with dependencies without Spring?
Instantiate the service directly in a unit test with mocks of its constructor parameters; no container needed.
Is Spring Boot Dependency Injection safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.