beginner~2h

IoC Container & Dependency Injection

This is the single idea everything else in Spring builds on. Get this genuinely clear and every annotation you meet later is just "a way of telling the container something," not new magic each time.

Learning objectives

  • Beginner: Recognize @Autowired on a field and understand it means "the container supplies this," not manual object construction.
  • Intermediate: Consistently use constructor injection for all required dependencies across a real project's services and repositories.
  • Advanced: Diagnose and resolve a circular dependency (two beans each requiring the other via constructor injection) by understanding why constructor injection surfaces it immediately at startup rather than allowing it silently.

◆ The problem

A BookController needs a BookService, which needs a BookRepository, which needs a database connection. Without a container, something has to construct all of these in the right order and hand them to each other — and if every class constructs its own dependencies with new internally, you can't swap a real repository for a test fake, you can't share one instance across the app, and every class is tightly coupled to concrete implementations of everything it depends on.

Inversion of Control flips the direction of that responsibility: instead of a class creating or looking up its own dependencies, those dependencies are handed to it from the outside, by a container. Dependency Injection is the specific technique Spring uses to achieve this — the container constructs a BookRepository, then a BookService (injecting the repository into it), then a BookController (injecting the service into it), all without any of those classes ever writing new BookRepository() themselves.

Without IoC, each class is responsible for constructing its own dependency chain. With IoC, the container constructs every bean once and injects already-built dependencies into whatever needs them.

// Constructor injection — PREFERRED @Service public class BookService { private final BookRepository bookRepository; public BookService(BookRepository bookRepository) { // no @Autowired needed on a single constructor this.bookRepository = bookRepository; } } // Setter injection — allows optional/reconfigurable dependencies @Service public class BookServiceSetter { private BookRepository bookRepository; @Autowired public void setBookRepository(BookRepository bookRepository) { this.bookRepository = bookRepository; } } // Field injection — discouraged, shown here to recognize it @Service public class BookServiceField { @Autowired private BookRepository bookRepository; }
StyleTrade-off
Constructor injectionDependencies are final and required — a bean can't exist in a half-wired state, and it's trivially testable by just calling the constructor directly with mocks.
Setter injectionAllows optional dependencies or reconfiguration after construction, at the cost of a bean that can briefly exist without them.
Field injectionLeast code, but hides the dependency from the constructor signature, makes plain unit testing without a Spring context awkward, and doesn't allow final fields.

▲ Pitfall

Field injection is extremely common in tutorials because it's short, but it's specifically discouraged in production code: it makes dependencies invisible from outside the class, makes plain (non-Spring) unit testing harder, and can hide circular dependency problems that constructor injection would surface immediately at startup instead of at runtime.

💻 Code example

// Constructor injection — PREFERRED @Service public class BookService { private final BookRepository bookRepository; public BookService(BookRepository bookRepository) { // no @Autowired needed on a single constructor this.bookRepository = bookRepository; } } // Setter injection — allows optional/reconfigurable dependencies @Service public class BookServiceSetter { private BookRepository bookRepository; @Autowired public void setBookRepository(BookRepository bookRepository) { this.bookRepository = bookRepository; } } // Field injection — discouraged, shown here to recognize it @Service public class BookServiceField { @Autowired private BookRepository bookRepository; }

The ApplicationContext is the concrete container instance running underneath every Spring Boot application — created automatically by SpringApplication.run() from Module 01 §4. It's responsible for the entire bean lifecycle explored fully in Module 03: discovering which classes should become beans, constructing them in dependency order, injecting their collaborators, and managing them until application shutdown.

✓ Quick recap

What does "Inversion of Control" actually invert? Who's responsible for constructing an object's dependencies — from the object itself, to an external container. Why is constructor injection generally preferred over field injection? It makes dependencies explicit and required (enabling final fields), simplifies testing without a Spring context, and surfaces circular dependencies at startup.

Want a visual for this concept?

Generate a diagram tailored to “IoC Container & Dependency Injection” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Beans, Configuration & Lifecycle← Back to all Spring Boot chapters