How Spring Dependency Injection Works

Intermediate
8 min read· Backend & Databases

Dependency injection (DI) is the core of Spring. Instead of your classes creating their own collaborators with new, Spring creates and supplies them for you. The Inversion of Control (IoC) container scans your code, builds a graph of beans (managed objects), and injects each bean where it is needed. This decouples your classes, makes them testable, and centralises configuration — the reason Spring code has almost no new keywords for services.

Think of DI as a kitchen that stocks itself

Imagine a chef who never shops. Whenever they need an ingredient, it is already on the counter — a stockroom manager (the IoC container) knows every recipe (bean) and places exactly what each dish needs within reach. The chef just declares "I need flour and eggs" (constructor parameters) and they appear. The chef never worries about where ingredients come from, so swapping a supplier (a different implementation) changes nothing in the kitchen.

Step by Step

1 / 5

Key Concepts

IoC Container

The ApplicationContext that creates, wires, and manages the lifecycle of beans. "Inversion of control" means the framework — not your code — decides when and how objects are created.

Constructor vs Field Injection

Constructor injection (preferred) makes dependencies explicit, final, and easy to test. Field injection (@Autowired on a field) is terser but hides dependencies and cannot be used without reflection in tests.

Bean Scope

How long a bean lives and how many exist. singleton (default) is one per context; prototype is a new one per request for it; request/session are web-scoped.

Qualifier

When two beans implement the same interface, @Qualifier or @Primary tells Spring which one to inject, resolving the ambiguity.

Key Facts

  • Prefer constructor injection: it makes dependencies obvious, supports final fields, and lets you instantiate the class in a unit test without Spring.
  • A singleton bean shared across threads must be stateless or thread-safe — storing per-request data in a singleton field is a classic concurrency bug.
  • Spring Boot auto-configuration is DI at scale: it defines beans conditionally so your app is wired up from dependencies on the classpath.

Real-World Applications

Swapping implementations for tests

Because a service receives its dependencies, a test can inject a mock PaymentGateway instead of the real one — no code change, just a different bean. This is the biggest practical payoff of DI.

Environment-specific beans

Using @Profile, Spring can inject an in-memory repository in dev and a real database repository in prod, wiring the right implementation based on the active profile.

Frequently Asked Questions

What is the difference between constructor and field injection?

Constructor injection passes dependencies through the constructor, making them explicit, final, and testable without Spring. Field injection sets them directly on private fields via reflection — shorter, but it hides dependencies, allows partially-constructed objects, and complicates testing. Spring recommends constructor injection.

What is a Spring bean?

A bean is any object that the Spring IoC container creates, wires, and manages. You declare beans with stereotype annotations (@Service, @Component) or @Bean methods, and Spring handles their instantiation, dependency injection, and lifecycle.

How does Spring resolve two beans of the same type?

It is ambiguous by default and fails to start. You resolve it by marking one bean @Primary (the default choice) or using @Qualifier("name") at the injection point to pick a specific bean.

Are singleton beans thread-safe?

Spring guarantees one instance, not thread safety. Since that single instance is shared across all threads, it must be stateless or use thread-safe state. Never store request-specific mutable data in a singleton field.

Related Topics