Spring Boot Dependency Injection
This skill captures the dependency injection approach promoted in this repository: constructor-first design, explicit optional collaborators, and deterministic configuration that keeps services testable and framework-agnostic.
Overview
- Prioritize constructor injection to keep dependencies explicit, immutable, and mockable.
- Treat optional collaborators through guarded setters or providers while documenting defaults.
- Resolve bean ambiguity intentionally through qualifiers, primary beans, and profiles.
- Validate wiring with focused unit tests before relying on Spring's TestContext framework.
When to Use
- Implement constructor injection for new
@Service, @Component, or @Repository classes.
- Replace legacy field injection while modernizing Spring modules.
- Configure optional or pluggable collaborators (feature flags, multi-tenant adapters).
- Audit bean definitions before adding integration tests or migrating Spring Boot versions.
Prerequisites
- Align project with Java 17+ and Spring Boot 3.5.x (or later) to leverage records and
@ServiceConnection.
- Keep build tooling ready to run
./gradlew test or mvn test for validation.
- Load supporting material from
./references/ when deeper patterns or samples are required.
Workflow
1. Map Collaborators
- Inventory constructors,
@Autowired members, and configuration classes.
- Classify dependencies as mandatory (must exist) or optional (feature-flagged, environment-specific).
2. Apply Constructor Injection
- Introduce constructors (or Lombok
@RequiredArgsConstructor) that accept every mandatory collaborator.
- Mark injected fields
final and protect invariants with Objects.requireNonNull if Lombok is not used.
- Update
@Configuration or @Bean factories to pass dependencies explicitly; consult ./references/reference.md for canonical bean wiring.
3. Handle Optional Collaborators
- Supply setters annotated with
@Autowired(required = false) or inject ObjectProvider<T> for lazy access.
- Provide deterministic defaults (for example, no-op implementations) and document them inside configuration modules.
- Follow
./references/examples.md#example-2-setter-injection-for-optional-dependencies for a full workflow.
4. Resolve Bean Selection
- Choose
@Primary for dominant implementations and @Qualifier for niche variants.
- Use profiles, conditional annotations, or factory methods to isolate environment-specific wiring.
- Reference
./references/reference.md#conditional-bean-registration for conditional and profile-based samples.
5. Validate Wiring
- Write unit tests that instantiate classes manually with mocks to prove Spring-free testability.
- Add slice or integration tests (
@WebMvcTest, @DataJpaTest, @SpringBootTest) only after constructor contracts are validated.
- Reuse patterns in
./references/reference.md#testing-with-dependency-injection to select the proper test style.
Examples
Basic Constructor Injection
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public User register(UserRegistrationRequest request) {
User user = User.create(request.email(), request.name());
userRepository.save(user);
emailService.sendWelcome(user);
return user;
}
}
- Instantiate directly in tests:
new UserService(mockRepo, mockEmailService); with no Spring context required.
Intermediate: Optional Dependency with Guarded Setter
@Service
public class ReportService {
private final ReportRepository reportRepository;
private CacheService cacheService = CacheService.noOp();
public ReportService(ReportRepository reportRepository) {
this.reportRepository = reportRepository;
}
@Autowired(required = false)
public void setCacheService(CacheService cacheService) {
this.cacheService = cacheService;
}
}
- Provide fallbacks such as
CacheService.noOp() to ensure deterministic behavior when the optional bean is absent.
Advanced: Conditional Configuration Across Modules
@Configuration
@Import(DatabaseConfig.class)
public class MessagingConfig {
@Bean
@ConditionalOnProperty(name = "feature.notifications.enabled", havingValue = "true")
public NotificationService emailNotificationService(JavaMailSender sender) {
return new EmailNotificationService(sender);
}
@Bean
@ConditionalOnMissingBean(NotificationService.class)
public NotificationService noopNotificationService() {
return NotificationService.noOp();
}
}
- Combine
@Import, profiles, and conditional annotations to orchestrate cross-cutting modules.
Additional worked examples (including tests and configuration wiring) are available in ./references/examples.md.
Best Practices
- Prefer constructor injection for mandatory dependencies; allow Spring 4.3+ to infer
@Autowired on single constructors.
- Encapsulate optional behavior inside dedicated adapters or providers instead of accepting
null pointers.
- Keep service constructors lightweight; extract orchestrators when dependency counts exceed four.
- Favor domain interfaces in the domain layer and defer framework imports to infrastructure adapters.
- Document bean names and qualifiers in shared constants to avoid typo-driven mismatches.
Constraints
- Avoid field injection and service locator patterns because they obscure dependencies and impede unit testing.
- Prevent circular dependencies by publishing domain events or extracting shared abstractions.
- Limit
@Lazy usage to performance-sensitive paths and record the deferred initialization risk.
- Do not add profile-specific beans without matching integration tests that activate the profile.
- Ensure each optional collaborator has a deterministic default or feature-flag handling path.
Reference Materials
- extended documentation covering annotations, bean scopes, testing, and anti-pattern mitigations
- progressive examples from constructor injection basics to multi-module configurations
- curated excerpts from the official Spring Framework documentation (constructor vs setter guidance, conditional wiring)
Related Skills
spring-boot-crud-patterns – service-layer orchestration patterns that rely on constructor injection.
spring-boot-rest-api-standards – controller-layer practices that assume explicit dependency wiring.
unit-test-service-layer – Mockito-based testing patterns for constructor-injected services.
1---2name: spring-boot-dependency-injection3description: Dependency injection workflow for Spring Boot projects covering constructor-first patterns, optional collaborator handling, bean selection, and validation practices.4---5
6# Spring Boot Dependency Injection
7
8This skill captures the dependency injection approach promoted in this repository: constructor-first design, explicit optional collaborators, and deterministic configuration that keeps services testable and framework-agnostic.
9
10## Overview
11
12- Prioritize constructor injection to keep dependencies explicit, immutable, and mockable.
13- Treat optional collaborators through guarded setters or providers while documenting defaults.
14- Resolve bean ambiguity intentionally through qualifiers, primary beans, and profiles.
15- Validate wiring with focused unit tests before relying on Spring's TestContext framework.
16
17## When to Use
18
19- Implement constructor injection for new `@Service`, `@Component`, or `@Repository` classes.
20- Replace legacy field injection while modernizing Spring modules.
21- Configure optional or pluggable collaborators (feature flags, multi-tenant adapters).
22- Audit bean definitions before adding integration tests or migrating Spring Boot versions.
23
24## Prerequisites
25
26- Align project with Java 17+ and Spring Boot 3.5.x (or later) to leverage records and `@ServiceConnection`.
27- Keep build tooling ready to run `./gradlew test` or `mvn test` for validation.
28- Load supporting material from `./references/` when deeper patterns or samples are required.
29
30## Workflow
31
32### 1. Map Collaborators
33- Inventory constructors, `@Autowired` members, and configuration classes.
34- Classify dependencies as mandatory (must exist) or optional (feature-flagged, environment-specific).
35
36### 2. Apply Constructor Injection
37- Introduce constructors (or Lombok `@RequiredArgsConstructor`) that accept every mandatory collaborator.
38- Mark injected fields `final` and protect invariants with `Objects.requireNonNull` if Lombok is not used.
39- Update `@Configuration` or `@Bean` factories to pass dependencies explicitly; consult `./references/reference.md` for canonical bean wiring.
40
41### 3. Handle Optional Collaborators
42- Supply setters annotated with `@Autowired(required = false)` or inject `ObjectProvider<T>` for lazy access.
43- Provide deterministic defaults (for example, no-op implementations) and document them inside configuration modules.
44- Follow `./references/examples.md#example-2-setter-injection-for-optional-dependencies` for a full workflow.
45
46### 4. Resolve Bean Selection
47- Choose `@Primary` for dominant implementations and `@Qualifier` for niche variants.
48- Use profiles, conditional annotations, or factory methods to isolate environment-specific wiring.
49- Reference `./references/reference.md#conditional-bean-registration` for conditional and profile-based samples.
50
51### 5. Validate Wiring
52- Write unit tests that instantiate classes manually with mocks to prove Spring-free testability.
53- Add slice or integration tests (`@WebMvcTest`, `@DataJpaTest`, `@SpringBootTest`) only after constructor contracts are validated.
54- Reuse patterns in `./references/reference.md#testing-with-dependency-injection` to select the proper test style.
55
56## Examples
57
58### Basic Constructor Injection
59```java
60@Service
61@RequiredArgsConstructor
62public class UserService {
63 private final UserRepository userRepository;
64 private final EmailService emailService;
65
66 public User register(UserRegistrationRequest request) {
67 User user = User.create(request.email(), request.name());
68 userRepository.save(user);
69 emailService.sendWelcome(user);
70 return user;
71 }
72}
73```
74- Instantiate directly in tests: `new UserService(mockRepo, mockEmailService);` with no Spring context required.
75
76### Intermediate: Optional Dependency with Guarded Setter
77```java
78@Service
79public class ReportService {
80 private final ReportRepository reportRepository;
81 private CacheService cacheService = CacheService.noOp();
82
83 public ReportService(ReportRepository reportRepository) {
84 this.reportRepository = reportRepository;
85 }
86
87 @Autowired(required = false)
88 public void setCacheService(CacheService cacheService) {
89 this.cacheService = cacheService;
90 }
91}
92```
93- Provide fallbacks such as `CacheService.noOp()` to ensure deterministic behavior when the optional bean is absent.
94
95### Advanced: Conditional Configuration Across Modules
96```java
97@Configuration
98@Import(DatabaseConfig.class)
99public class MessagingConfig {
100
101 @Bean
102 @ConditionalOnProperty(name = "feature.notifications.enabled", havingValue = "true")
103 public NotificationService emailNotificationService(JavaMailSender sender) {
104 return new EmailNotificationService(sender);
105 }
106
107 @Bean
108 @ConditionalOnMissingBean(NotificationService.class)
109 public NotificationService noopNotificationService() {
110 return NotificationService.noOp();
111 }
112}
113```
114- Combine `@Import`, profiles, and conditional annotations to orchestrate cross-cutting modules.
115
116Additional worked examples (including tests and configuration wiring) are available in `./references/examples.md`.
117
118## Best Practices
119
120- Prefer constructor injection for mandatory dependencies; allow Spring 4.3+ to infer `@Autowired` on single constructors.
121- Encapsulate optional behavior inside dedicated adapters or providers instead of accepting `null` pointers.
122- Keep service constructors lightweight; extract orchestrators when dependency counts exceed four.
123- Favor domain interfaces in the domain layer and defer framework imports to infrastructure adapters.
124- Document bean names and qualifiers in shared constants to avoid typo-driven mismatches.
125
126## Constraints
127
128- Avoid field injection and service locator patterns because they obscure dependencies and impede unit testing.
129- Prevent circular dependencies by publishing domain events or extracting shared abstractions.
130- Limit `@Lazy` usage to performance-sensitive paths and record the deferred initialization risk.
131- Do not add profile-specific beans without matching integration tests that activate the profile.
132- Ensure each optional collaborator has a deterministic default or feature-flag handling path.
133
134## Reference Materials
135
136- [extended documentation covering annotations, bean scopes, testing, and anti-pattern mitigations](references/reference.md)
137- [progressive examples from constructor injection basics to multi-module configurations](references/examples.md)
138- [curated excerpts from the official Spring Framework documentation (constructor vs setter guidance, conditional wiring)](references/spring-official-dependency-injection.md)
139
140## Related Skills
141
142- `spring-boot-crud-patterns` – service-layer orchestration patterns that rely on constructor injection.
143- `spring-boot-rest-api-standards` – controller-layer practices that assume explicit dependency wiring.
144- `unit-test-service-layer` – Mockito-based testing patterns for constructor-injected services.