← All articles
Backend1 min read

Dependency Injection in Spring Explained

Constructor injection, beans, and scopes — a practical tour of how Spring's IoC container wires your application together.

S

Swapnika Voora

Author

Dependency injection (DI) is the core idea behind Spring. Instead of objects creating their own collaborators, the framework hands them in. This keeps components decoupled and trivial to test.

Prefer constructor injection

Field injection with @Autowired is convenient but hides dependencies and makes testing harder. Constructor injection makes dependencies explicit and allows final fields.

OrderService.java
@Service
public class OrderService {
 
    private final PaymentGateway gateway;
    private final OrderRepository repository;
 
    public OrderService(PaymentGateway gateway, OrderRepository repository) {
        this.gateway = gateway;
        this.repository = repository;
    }
 
    public Order place(OrderRequest request) {
        gateway.charge(request.amount());
        return repository.save(new Order(request));
    }
}

What makes a bean

A bean is any object the Spring container manages. You declare them with stereotype annotations or @Bean methods in a configuration class.

AppConfig.java
@Configuration
public class AppConfig {
 
    @Bean
    public PaymentGateway paymentGateway(RestClient.Builder builder) {
        return new StripePaymentGateway(builder.build());
    }
}

Bean scopes

Most beans are singletons — one instance shared across the app. Use other scopes deliberately.

  • singleton (default): one shared instance.
  • prototype: a new instance every time it's requested.
  • request / session: web-scoped instances tied to an HTTP lifecycle.

Why it matters for testing

Because dependencies are passed in, tests can swap real collaborators for mocks without touching the class under test.

OrderServiceTest.java
@Test
void placesOrderAndCharges() {
    var gateway = mock(PaymentGateway.class);
    var repo = mock(OrderRepository.class);
    var service = new OrderService(gateway, repo);
 
    service.place(new OrderRequest(BigDecimal.TEN));
 
    verify(gateway).charge(BigDecimal.TEN);
}

Constructor injection plus mocks gives you fast, isolated unit tests with no Spring context required.

#java#spring#dependency-injection#architecture

More in Backend