Back to Backend Development

spring-boot-dependency-injection

Spring BootDependency InjectionJavaBackend DevelopmentArchitectureTestingConstructor InjectionBean Management
282📄 MIT🕒 2026-06-15Source ↗

Install this skill

npx skills add giuseppe-trisciuoglio/developer-kit

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

This skill mandates a strictly constructor-based injection strategy for Spring Boot applications to minimize framework coupling. By enforcing final fields and immutable service design, it ensures that components remain independently testable without loading the full Spring ApplicationContext. The approach prioritizes explicit dependency declaration, requiring mandatory collaborators to be passed via class constructors while handling optional variants through guarded setters or ObjectProvider. It moves away from field-level autowiring, which often hides structural dependencies and complicates unit testing. By promoting clear wiring logic, the skill helps developers maintain predictable, decoupled codebases where bean lifecycle management is transparent, modular, and easy to audit. This focus on standard Java patterns ensures that your services are framework-agnostic by default, simplifying debugging and improving the long-term maintainability of enterprise Java systems.

When to Use This Skill

  • Modernizing legacy Spring components that rely on field autowiring
  • Building services that require rapid unit tests without overhead of Spring TestContext
  • Configuring pluggable feature flags where components may or may not exist at runtime
  • Managing environment-specific bean configurations across microservice modules

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • refactor my spring component to use constructor injection
  • how to handle optional spring beans without using @Autowired
  • write a unit test for my spring service without loading the context
  • configure a fallback bean for an optional dependency
  • apply constructor-first design to my spring configuration

Pro Tips

  • 💡Always make injected dependencies `final` when using constructor injection to ensure immutability and prevent accidental reassignment.
  • 💡Leverage Java Records (Java 17+) for data-carrying DTOs and even simple service components to get constructor injection, `equals()`, `hashCode()`, and `toString()` for free.
  • 💡Prioritize `@Qualifier` for resolving bean ambiguity over `@Primary` as it offers more explicit control and reduces hidden dependencies, improving clarity.

What this skill does

  • Refactors legacy field injection to explicit constructor-based dependency management
  • Establishes deterministic fallback behaviors for optional service components
  • Implements manual, mock-based unit testing that bypasses Spring context initialization
  • Orchestrates complex bean creation using conditional annotations and property-based logic
  • Enforces immutability for injected service dependencies using final fields

When not to use it

  • Projects requiring highly dynamic, runtime-injected bean proxying that prohibits final fields
  • Rapid prototyping where strict architectural discipline slows development velocity

Example workflow

  1. Inventory all current field autowiring and identify missing constructor parameters
  2. Update class structures to include mandatory dependencies as final fields
  3. Configure bean factories to pass dependencies explicitly through constructor arguments
  4. Implement ObjectProvider or guarded setters for optional feature-flagged components
  5. Write isolated unit tests passing mocks directly to the class constructor
  6. Verify bean wiring via integration slice tests only after unit validation

Prerequisites

  • Java 17 or higher
  • Spring Boot 3.5.x or later
  • Maven or Gradle build system configured for test execution

Pitfalls & limitations

  • !Constructor parameter bloat occurring when classes have too many responsibilities
  • !Circular dependency issues that become immediately apparent once moved to constructor injection
  • !Accidentally omitting @RequiredArgsConstructor in Lombok-based projects, causing compilation errors

FAQ

Why avoid field injection?
Field injection hides dependencies, making classes harder to instantiate for unit tests and masking potential null pointer exceptions during component initialization.
How do I handle optional dependencies?
Use ObjectProvider<T> for lazy access or provide a no-op implementation via a setter method annotated with @Autowired(required = false).
Does this require Spring TestContext?
No. By using constructor injection, you can instantiate your service directly in JUnit tests using mock objects, completely bypassing the Spring container.
What happens if I have circular dependencies?
Circular dependencies are a symptom of poor design. Moving to constructor injection exposes these cycles immediately, forcing you to refactor into smaller, cleaner components.

How it compares

Unlike standard code generation that blindly adds @Autowired to every field, this skill enforces an architectural pattern that improves testability and keeps your domain logic strictly separated from the Spring framework's internal mechanics.

Source & trust

282 stars📄 MIT🕒 Updated 2026-06-15
📄 Full skill instructions — original source: giuseppe-trisciuoglio/developer-kit
# 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](references/reference.md)
- [progressive examples from constructor injection basics to multi-module configurations](references/examples.md)
- [curated excerpts from the official Spring Framework documentation (constructor vs setter guidance, conditional wiring)](references/spring-official-dependency-injection.md)

## 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.

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/spring-boot-dependency-injection/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/giuseppe-trisciuoglio/developer-kit/spring-boot-dependency-injection/SKILL.md
  • Cursor: ~/.cursor/skills/giuseppe-trisciuoglio/developer-kit/spring-boot-dependency-injection/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/giuseppe-trisciuoglio/developer-kit/spring-boot-dependency-injection/SKILL.md

🚀 Install with CLI:
npx skills add giuseppe-trisciuoglio/developer-kit

Read the Master Guide: Mastering Agent Skills

Related Skill Units

Recommended Rules

View more rules

Recommended Workflows

View more workflows

Recommended MCP Servers

View more MCP servers

Take It Further

Maximize your productivity with these powerful resources

📋

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
📖

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid backend development issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under Backend Development and is published by Giuseppe Trisciuoglio, maintained in giuseppe-trisciuoglio/developer-kit.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.