Skip to content
Spring Boot fundamentals 3 min read

Dependency Injection & IoC

Dependency Injection (DI) is the core pattern that makes Spring applications modular and testable. Rather than each class creating its own collaborators, the framework supplies them. This page explains how Spring’s container works and how to use it idiomatically.

The IoC container

Inversion of Control (IoC) means the framework, not your code, controls object creation and wiring. Spring’s ApplicationContext is the IoC container: it instantiates beans, resolves their dependencies, and manages their lifecycle. You describe what you need; the container decides how to assemble it.

The benefit is decoupling. A controller depends on a UserService interface, not a concrete implementation, so you can swap implementations or inject mocks in tests without changing the controller.

Declaring beans with stereotypes

The stereotype annotations mark classes for component scanning. They are functionally similar but communicate intent.

AnnotationIntended for
@ComponentAny Spring-managed bean
@ServiceBusiness logic
@RepositoryData access (adds exception translation)
@Controller / @RestControllerWeb endpoints
@Service
public class UserService {
    public String greet(String name) {
        return "Hello, " + name;
    }
}

Constructor vs field injection

Spring can inject dependencies through constructors, fields, or setters. Constructor injection is strongly recommended.

@RestController
public class UserController {

    private final UserService userService;

    // Constructor injection — no @Autowired needed for a single constructor
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/greet/{name}")
    public String greet(@PathVariable String name) {
        return userService.greet(name);
    }
}

Contrast this with field injection, which is concise but problematic:

@RestController
public class UserController {
    @Autowired
    private UserService userService; // discouraged
}

Warning: Field injection hides dependencies, prevents final fields, and makes the class hard to instantiate in plain unit tests. Constructor injection makes dependencies explicit and supports immutability.

Since Spring 4.3, @Autowired is optional when a class has a single constructor, so the recommended style is also the cleanest.

@Bean and @Configuration

When you cannot annotate a class (for example, a type from a third-party library), define it in a @Configuration class with @Bean methods.

@Configuration
public class AppConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper().findAndRegisterModules();
    }
}

The method name becomes the bean name, and its return value is the singleton instance the container manages.

Bean scopes

By default every bean is a singleton: one instance per container. Spring supports other scopes for specialized needs.

ScopeLifetime
singleton (default)One per application context
prototypeNew instance on every injection/lookup
requestOne per HTTP request (web apps)
sessionOne per HTTP session
@Service
@Scope("prototype")
public class ReportBuilder { }

Tip: Keep singletons stateless. Shared mutable state on a singleton bean is a common source of concurrency bugs because the same instance serves all threads.

Best Practices

  • Prefer constructor injection with final fields for immutability and easy testing.
  • Inject interfaces, not concrete classes, to keep components loosely coupled.
  • Avoid field injection except in throwaway code; never use it in production beans.
  • Keep singleton beans stateless and thread-safe.
  • Use @Bean factory methods only for types you cannot annotate; prefer stereotypes otherwise.
  • Resolve ambiguity with @Qualifier or @Primary when multiple beans of the same type exist, rather than relying on field names.
Last updated June 1, 2026
Was this helpful?