Dependency Injection & IoC
Dependency Injection (DI) is the pattern the entire Spring framework is built around — every other pattern in this section assumes it. Instead of an object creating or looking up its collaborators, those collaborators are supplied to it from the outside. The result is loosely coupled, independently testable components.
DI is one concrete form of the broader principle of Inversion of Control (IoC), sometimes called the Hollywood Principle: “Don’t call us, we’ll call you.” Your classes no longer drive the lifecycle and wiring; the container does.
The problem DI solves
Without DI, a class hard-codes how it builds its dependencies:
public class OrderService {
// Tightly coupled: OrderService owns construction of its collaborators.
private final PaymentClient paymentClient = new StripePaymentClient();
private final OrderRepository repository = new JpaOrderRepository();
}
This is rigid. You cannot swap StripePaymentClient for a test double without editing the class, and OrderService now knows construction details it should not care about.
With DI, the dependencies arrive through the constructor and the class is agnostic about which implementation it receives:
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class OrderService {
private final PaymentClient paymentClient; // injected
private final OrderRepository repository; // injected
public Order place(OrderRequest request) {
paymentClient.charge(request.amount());
return repository.save(new Order(request));
}
}
@RequiredArgsConstructor generates a constructor for the final fields, and Spring autowires it. OrderService no longer knows or cares that the payment client talks to Stripe.
How the container wires beans
The IoC container scans for stereotype-annotated classes, registers each as a bean definition, then satisfies every constructor parameter by looking up a matching bean by type:
@Component
public class StripePaymentClient implements PaymentClient { /* ... */ }
@Repository
public class JpaOrderRepository implements OrderRepository { /* ... */ }
At startup the container constructs StripePaymentClient and JpaOrderRepository, then injects them into OrderService. You wrote three classes and zero wiring code — the container assembled the object graph.
Note: Since Spring 4.3, a class with a single constructor needs no
@Autowiredannotation; the container uses that constructor automatically. Lombok’s@RequiredArgsConstructortherefore gives you injection with no boilerplate at all.
Why constructor injection
Spring supports field, setter, and constructor injection, but constructor injection is the recommended default.
| Style | Looks like | Drawbacks |
|---|---|---|
| Constructor | final fields set in constructor | None significant — preferred |
| Setter | @Autowired on a setter | Object can exist half-initialised; mutable |
| Field | @Autowired on a field | Cannot be final; hard to test without reflection |
Constructor injection guarantees a bean is fully initialised and immutable once built, makes required dependencies explicit, and surfaces circular dependencies at startup rather than hiding them.
Testability — the real payoff
Because dependencies are passed in, a unit test can supply mocks without any Spring context at all:
import static org.mockito.Mockito.*;
class OrderServiceTest {
@Test
void placesOrderAndCharges() {
PaymentClient payment = mock(PaymentClient.class);
OrderRepository repo = mock(OrderRepository.class);
OrderService service = new OrderService(payment, repo);
service.place(new OrderRequest(42));
verify(payment).charge(42);
verify(repo).save(any(Order.class));
}
}
This plain JUnit test runs in milliseconds because there is no container, no Stripe, and no database — only the collaborators you chose to inject. That decoupling is the whole point of the pattern.
Tip: Program to interfaces (
PaymentClient,OrderRepository) rather than concrete classes. The DI container can then swap implementations per profile or per test, which is exactly the strategy pattern in action.
Relationship to other patterns
DI is the mechanism that makes the rest of Spring’s patterns practical. Singletons are the default scope of injected beans; factories produce the beans that get injected; strategies are alternative implementations the container picks between. Master DI and the others follow.