What is Dependency Injection (DI)?
Dependency Injection (DI) is a design pattern implementing Inversion of Control (IoC), where Spring automatically provides the required dependencies to a class instead of the class instantiating them directly with the new keyword.
Without Dependency Injection
PaymentService service = new PaymentService();Tight Coupling: The caller is hard-coded to a concrete implementation. Impossible to swap implementations or mock in isolated unit tests.
With Dependency Injection
Spring IoC Container injects the object for you.Loose Coupling: The caller depends only on an abstraction/interface. Spring handles lifecycle, wiring, and configuration.
▼ ▼
The 3 Types of Dependency Injection
Spring supports 3 distinct injection patterns, but only one is considered the gold standard for production code:
RECOMMENDED1. Constructor Injection
- • Dependencies passed explicitly through the constructor
- • Best for immutability: Allows fields to be marked
final - • Easy unit testing: Can instantiate with standard
new OrderService(mockService)without Spring reflection - • Prevents
NullPointerExceptionat compile-time by enforcing required fields
OPTIONAL USE2. Setter Injection
- • Dependencies passed via public setter methods (
setPaymentService(...)) - • Useful for optional dependencies or breaking rare circular dependency deadlocks
- • Drawback: Objects are mutable and can be modified after construction
NOT RECOMMENDED3. Field Injection (@Autowired on private fields)
- • Injected directly into private fields using reflection
- • Harder to unit test: Requires Spring TestRunner or Mockito reflection hacks
- • Hides class dependencies and violates the Single Responsibility Principle
- • Makes immutable
finalfields impossible
Spring Managed Beans
Spring manages objects called Beans inside the IoC Container. There are two primary ways to register beans into the container:
1. Stereotype Annotations (Automatic Scan)
- @Component: Generic Spring bean
- @Service: Business logic layer
- @Repository: DAO / Database persistence
- @Controller / @RestController: HTTP web layer
2. Java Config (@Configuration + @Bean)
Used when you need to configure third-party libraries (e.g. AWS S3 client, RedisTemplate) whose source code you cannot annotate directly with @Service.
// Creating Beans via @Configuration and @Bean
package com.example.demo.config;
import com.example.demo.service.PaymentService;
import com.example.demo.service.StripePaymentService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public PaymentService paymentService() {
// Useful for 3rd-party library classes or custom initialization
return new StripePaymentService("api_key_live_xyz");
}
}@Qualifier vs @Primary: Resolving Bean Ambiguity
When multiple beans implement the exact same interface, Spring gets confused and throws aNoUniqueBeanDefinitionException at startup.
// Resolving Bean Ambiguity: @Primary and @Qualifier
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
public interface PaymentService {
void pay(double amount);
}
// 1. Default fallback bean using @Primary
@Service
@Primary
public class CreditCardPayment implements PaymentService {
@Override
public void pay(double amount) {
System.out.println("Paid via Credit Card: " + amount);
}
}
@Service("paypalPayment")
public class PaypalPayment implements PaymentService {
@Override
public void pay(double amount) {
System.out.println("Paid via PayPal: " + amount);
}
}
// 2. Specific selection using @Qualifier
@Service
public class CheckoutService {
private final PaymentService paymentService;
// Explicitly chooses "paypalPayment" bean over default @Primary
public CheckoutService(@Qualifier("paypalPayment") PaymentService paymentService) {
this.paymentService = paymentService;
}
}The Recommended Production Standard
// Recommended: Constructor Injection with final fields
package com.example.demo.service;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
// 1. Injected dependency is immutable (thread-safe)
private final PaymentService paymentService;
// 2. Spring 4.3+: @Autowired is optional on single constructor!
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void processOrder(double amount) {
paymentService.pay(amount);
}
}Clean Architecture Starts with Clean DI
Dependency Injection is not just a Spring trick — it is the backbone of writing decoupled, testable, and maintainable enterprise software.
Find this Dependency Injection breakdown useful?
Hit like if you found it helpful, and share it with your network!