Dependency Injection

Master Spring's three DI styles — constructor, setter, and field injection — and understand why constructor injection is the only correct choice for production code.

Last reviewed: January 2025

·Spring Boot 2.7Spring Boot 3.xSpring 6
✅
Quick Understanding: Dependency Injection (DI) is how the IoC container provides dependencies to your beans. Instead of a class creating its own dependencies with "new", Spring injects them. There are three ways: constructor injection (best), setter injection (optional deps), and field injection (@Autowired on a field — avoid this in production code).

🔍 What Is It?

Dependency Injection is the mechanism by which the Spring IoC Container provides a bean's dependencies.

Three injection types:

1. Constructor Injection (recommended)

@Service
public class OrderService {
    private final OrderRepository repository; // final — immutable
    
    public OrderService(OrderRepository repository) { // Spring calls this
        this.repository = repository;
    }
}

2. Setter Injection (optional dependencies)

@Service
public class NotificationService {
    private EmailService emailService; // optional
    
    @Autowired(required = false)
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
}

3. Field Injection (avoid)

@Service
public class UserService {
    @Autowired // Don't do this — untestable, hides dependencies
    private UserRepository repository;
}

🎯 Why Do We Need It?

DI is what makes Spring applications testable and maintainable:

Without DI (tight coupling):

public class OrderService {
    private OrderRepository repository = new JpaOrderRepository(); // hardcoded!
    // Can't test without a real database
    // Can't swap implementations
}

With DI (loose coupling):

public class OrderService {
    private final OrderRepository repository; // interface, not implementation
    
    public OrderService(OrderRepository repository) {
        this.repository = repository;
    }
    // In tests: inject MockOrderRepository
    // In production: inject JpaOrderRepository
    // Change implementations without touching OrderService
}

🏗️ What Happens in Real Projects?

In a real enterprise Spring Boot application:

  • →Every @Service uses constructor injection to receive its @Repository dependencies
  • →Every @Controller uses constructor injection to receive its @Service dependencies
  • →@Configuration beans inject other beans via constructor or method parameters
  • →Tests inject mock implementations via constructor — no Spring context needed

The pattern is so consistent that Lombok's @RequiredArgsConstructor is commonly used to auto-generate constructors for all final fields.

⚙️ How It Works

Constructor injection resolution:

  1. Spring needs to create OrderService
  2. Reads its constructor: requires OrderRepository
  3. Looks up OrderRepository bean in the container
  4. Creates OrderRepository first (if not yet created)
  5. Calls new OrderService(orderRepository)
  6. OrderService is ready

@Autowired is optional for single-constructor classes:

Since Spring 4.3, if a class has exactly one constructor, Spring automatically uses it for injection — no @Autowired needed. This is the modern style.

@Qualifier for disambiguation:

When multiple beans of the same type exist, use @Qualifier("beanName") to specify which one.

💻 Example

Dependency Injection Patterns — Constructor injection, qualifiers, and testing with DI.

java
@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    private final NotificationService notificationService;
    private final AppProperties appProperties;

    // No @Autowired needed — Spring detects single constructor
    public OrderService(
            OrderRepository orderRepository,
            PaymentService paymentService,
            InventoryService inventoryService,
            NotificationService notificationService,
            AppProperties appProperties) {
        this.orderRepository = orderRepository;
        this.paymentService = paymentService;
        this.inventoryService = inventoryService;
        this.notificationService = notificationService;
        this.appProperties = appProperties;
    }

    @Transactional
    public Order placeOrder(CreateOrderRequest request) {
        inventoryService.reserve(request.getItems());
        Order order = orderRepository.save(Order.from(request));
        paymentService.charge(order);
        notificationService.sendConfirmation(order);
        return order;
    }
}

🤔 Common Confusions

❓ Why is field injection (@Autowired on fields) bad?

Three reasons: (1) You can't write unit tests without starting a Spring context — there's no way to inject mocks via constructor. (2) Dependencies are hidden — you can't see what a class needs without reading its internals. (3) You can't make fields final, so dependencies could theoretically change. Constructor injection solves all three.

❓ Do I need @Autowired on my constructor?

Not since Spring 4.3. If your class has exactly one constructor, Spring uses it automatically. @Autowired is only needed if you have multiple constructors and want Spring to use a specific one. Modern code omits @Autowired entirely.

❓ What is the difference between @Autowired, @Inject, and @Resource?

@Autowired is Spring-specific (type-based matching). @Inject is JSR-330 standard (same as @Autowired). @Resource is JSR-250 (name-based matching by default). In Spring Boot projects, use @Autowired or (better) no annotation with constructor injection. @Inject and @Resource are for portability that's rarely needed.

🚨 Common Production Issues

Field injection making unit tests require full Spring context — migrate to constructor injection

Circular dependency via constructor injection — refactor the design; circular deps indicate poor separation of concerns

Too many constructor parameters (>5) — sign the class has too many responsibilities, split it

@Autowired not working because the class is created with "new" instead of by Spring

Optional dependency not handled — use Optional<T> or @Autowired(required=false) with null check

✅ Best Practices

Always use constructor injection — never field injection

Make injected fields final — prevents accidental reassignment

Use Lombok @RequiredArgsConstructor to reduce boilerplate

Program to interfaces, not implementations — inject UserRepository, not JpaUserRepository

If a class has more than 4-5 injected dependencies, it's doing too much — split it

Use @Primary for the default implementation, @Qualifier for specific ones

🏛️ Architect's Note

Senior Architect's Perspective

Constructor injection is not just a style preference — it's an architectural constraint that forces good design. When a class has 8 constructor parameters, it's immediately obvious it violates the Single Responsibility Principle. Field injection hides this problem. Teams that enforce constructor injection naturally write smaller, more focused classes. This is why senior engineers insist on it.

💬 Frequently Asked Questions

Can I inject a List of all beans implementing an interface?

Yes — inject List<NotificationService> and Spring injects all beans implementing that interface. Similarly, inject Map<String, NotificationService> to get a map of bean name to bean. This is the Strategy pattern with Spring — very powerful for plugin-style architectures.

What is ObjectProvider<T>?

ObjectProvider<T> is Spring's lazy and optional injection mechanism. It's injected like any bean but doesn't throw an exception if the bean doesn't exist. Use getIfAvailable() for optional deps, getObject() to get the bean (throws if absent), or stream() to iterate all matching beans. Better than @Autowired(required=false) for optional dependencies.

🎯 Key Takeaways

1Constructor injection is the correct approach — makes dependencies explicit and testable
2Field injection (@Autowired on fields) should never be used in production code
3@Autowired is optional since Spring 4.3 for single-constructor classes
4Use @Primary for default beans, @Qualifier for specific ones when multiple exist
5Inject List<T> to get all beans of a type — enables Strategy pattern
6Too many constructor parameters is a code smell — split the class

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Spring Boot references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Spring Boot learning path

Browse the full Spring Boot topic collection and learning order on SplashRide.

Spring Boot Interview Prep

Practice Spring Boot interview questions and scenario-based answers.

Backend Engineer Career Path

A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

IoC Container

Understand the Spring IoC Container — the heart of Spring that manages object creation, dependency wiring, and bean lifecycle in every enterprise application.

Bean Lifecycle

Understand the complete Spring bean lifecycle — from instantiation through initialization to destruction — and how to hook into each phase for initialization and cleanup.