spring-boot-dependency-injection
giuseppe-trisciuoglio/developer-kit
Constructor-first dependency injection patterns for Spring Boot services and configurations.
What is spring-boot-dependency-injection?
Provides best practices for Spring Boot dependency injection, emphasizing constructor injection for mandatory dependencies, optional collaborator handling via ObjectProvider or no-op implementations, and bean selection with @Primary and @Qualifier. Use when building new services, refactoring away field injection, or resolving wiring ambiguities.
- Implement constructor-first injection for mandatory collaborators
- Handle optional dependencies with ObjectProvider or conditional beans
- Select between multiple beans using @Primary and @Qualifier annotations
- Validate wiring with minimal context tests before full integration
- Replace field injection with testable constructor patterns
- Resolve circular dependencies and brittle startup failures
How to install spring-boot-dependency-injection
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill spring-boot-dependency-injectionHow to use spring-boot-dependency-injection
- 1.Identify mandatory vs. optional collaborators for each class
- 2.Inject mandatory dependencies through the constructor with final fields
- 3.Use ObjectProvider<T> or @ConditionalOnProperty for optional behavior
- 4.Apply @Primary for default bean implementations and @Qualifier for named variants
- 5.Keep wiring logic in @Configuration classes, not business code
- 6.Write a minimal context test to verify the bean loads, then add constructor-based unit tests
Use cases
- Creating a new @Service or @Repository with clear dependency requirements
- Refactoring legacy code that uses field injection to constructor injection
- Handling optional integrations like email senders or notification gateways without null checks
- Disambiguating multiple bean implementations of the same type in configuration
- Testing service behavior directly without Spring context using constructor-based instantiation
- Spring Boot developers building new services
- Teams refactoring legacy Spring applications
- Developers writing testable, loosely-coupled code
- Architects designing configuration and composition strategies
spring-boot-dependency-injection FAQ
ObjectProvider provides lazy access and cleaner null handling. Use it for optional collaborators that may not always be available. Avoid @Autowired(required=false) because it leaves nullable fields ambiguous; instead use ObjectProvider.getIfAvailable() with a no-op fallback.
Use @Primary to mark the default implementation and @Qualifier("name") for named variants. Keep qualifier names stable and easy to grep. If selection logic becomes complex, move it into a dedicated @Configuration class.
Start with constructor-based unit tests (no Spring needed) to verify business logic. Add a minimal context test to validate wiring loads correctly. Reserve @SpringBootTest only for container-wide validation or when MVC/JPA/messaging integration must be verified.
This signals a design problem. Consider breaking the class into smaller, more focused services or introducing a facade. Too many dependencies often means the abstraction is doing too much.
Circular dependencies usually indicate a design issue, not a wiring problem. Refactor to separate concerns or introduce an abstraction layer. Avoid using @Lazy as a workaround; it masks the underlying design problem.
Full instructions (SKILL.md)
Source of truth, from giuseppe-trisciuoglio/developer-kit.
name: spring-boot-dependency-injection description: Provides dependency injection patterns for Spring Boot projects, including constructor-first design, optional collaborator handling, bean selection, and wiring validation. Use when creating services and configurations, replacing field injection, or troubleshooting ambiguous or fragile Spring wiring. allowed-tools: Read, Write, Edit, Bash, Glob, Grep
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:
- Verify the bean loads with a minimal context test:
@SpringBootTest @ContextConfiguration(classes = UserService.class) class UserServiceWiringTest { @Autowired UserService userService; @Test void serviceIsInstantiated() { assertNotNull(userService); } } - Run constructor-based unit tests for service behavior (no Spring needed).
- Add slice tests only when MVC, JPA, or messaging integration must be verified.
- Reserve
@SpringBootTestfor 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
Related skills
More from giuseppe-trisciuoglio/developer-kit and the wider catalog.

spring-boot-event-driven-patterns
Event-Driven Architecture patterns for Spring Boot with domain events, Kafka, and transactional outbox.

spring-boot-openapi-documentation
Generate OpenAPI 3.0 documentation and Swagger UI for Spring Boot 3.x REST APIs with SpringDoc

spring-boot-project-creator
Creates and scaffolds a new Spring Boot project (3.x or 4.x) by downloading from Spring Initializr, generating package structure (DDD or Layered architecture), configuring JPA, SpringDoc OpenAPI, and Docker Compose services (PostgreSQL, Redis, MongoDB). Use when creating a new Java Spring Boot project from scratch, bootstrapping a microservice, or initializing a backend application.

spring-boot-resilience4j
Fault tolerance patterns for Spring Boot 3.x using Resilience4j—circuit breakers, retries, rate limiting, and more.

spring-boot-rest-api-standards
REST API design standards and best practices for Spring Boot projects

spring-boot-saga-pattern
Distributed transaction patterns for Spring Boot microservices using Saga Pattern with choreography or orchestration.