Skip to content
Spring Boot sb core 4 min read

IoC Container

The IoC container is the heart of the Spring Framework. It creates the objects your application needs, wires them together, and manages them for their entire lifetime. Instead of your code constructing and connecting collaborators by hand, you describe what you need and the container assembles it for you.

What is Inversion of Control?

Inversion of Control (IoC) is a design principle in which the control over object creation and wiring is inverted — moved out of your classes and into a framework. In a traditional program a class calls new to build its dependencies. With IoC, the framework constructs those dependencies and hands them to the class.

The most common form of IoC is Dependency Injection (DI): the container injects a class’s collaborators rather than letting the class fetch them. The payoff is loose coupling. A UserController declares that it needs a UserService; it does not care which concrete implementation arrives, so you can swap implementations or inject test doubles without touching the controller.

Note: IoC is the principle; DI is the technique Spring uses to implement it. See Dependency Injection for the injection styles themselves.

The Spring container

The container reads bean definitions — from annotated classes, @Configuration methods, or (historically) XML — instantiates each bean, resolves its dependencies, and stores it in a registry keyed by name and type. In Spring Boot the container is created and configured automatically when you call SpringApplication.run(...).

@SpringBootApplication
public class StoreApplication {
    public static void main(String[] args) {
        // run() builds the container and returns it
        ConfigurableApplicationContext context =
                SpringApplication.run(StoreApplication.class, args);

        // Retrieve a bean from the container by type
        OrderService orders = context.getBean(OrderService.class);
        orders.placeOrder("SKU-42");
    }
}

The object returned by run() is the IoC container. In a typical web application you rarely touch it directly — Spring injects beans for you — but it is useful for bootstrapping and tooling.

ApplicationContext vs BeanFactory

Spring exposes two container interfaces. BeanFactory is the minimal, lazy core; ApplicationContext extends it with enterprise features and is what you use in practice.

FeatureBeanFactoryApplicationContext
Bean instantiation / wiringYesYes
Bean instantiation timingLazy (on first request)Eager for singletons at startup
Automatic BeanPostProcessor registrationManualAutomatic
Internationalization (MessageSource)NoYes
Event publishing (ApplicationEvent)NoYes
Annotation config (@Autowired, @Value)LimitedFull
Typical useMemory-constrained / embeddedDefault for Spring Boot apps

Tip: Always prefer ApplicationContext. BeanFactory exists mainly for legacy and low-footprint scenarios; Spring Boot wires an ApplicationContext for you.

Common ApplicationContext implementations include AnnotationConfigApplicationContext (Java-config, non-web) and AnnotationConfigServletWebServerApplicationContext (auto-selected for Spring Boot web apps).

How beans are wired

You declare beans either with stereotype annotations picked up by component scanning, or with @Bean factory methods inside a @Configuration class. The container then resolves dependencies by type, injecting them through constructors.

@Service
public class OrderService {

    private final InventoryService inventory;

    // Single constructor — Spring injects InventoryService automatically
    public OrderService(InventoryService inventory) {
        this.inventory = inventory;
    }

    public void placeOrder(String sku) {
        if (inventory.isAvailable(sku)) {
            System.out.println("Order placed for " + sku);
        }
    }
}

@Service
public class InventoryService {
    public boolean isAvailable(String sku) {
        return true;
    }
}

When the context starts, it sees both @Service beans, notices that OrderService requires an InventoryService, and supplies the managed instance. No new keyword and no lookup code appear in your business logic.

For types you cannot annotate — third-party classes such as RestClient — register them with a factory method instead:

@Configuration
public class AppConfig {

    @Bean
    public RestClient restClient() {
        return RestClient.create("https://api.example.com");
    }
}

The method name (restClient) becomes the bean name, and the returned object is the singleton the container manages.

Looking beans up programmatically

Most of the time you let Spring inject beans, but the context also supports direct lookup, which is handy in main methods, tests, and tooling.

ApplicationContext context =
        new AnnotationConfigApplicationContext(AppConfig.class);

// By type
RestClient client = context.getBean(RestClient.class);

// By name
RestClient sameClient = (RestClient) context.getBean("restClient");

// By name and type (preferred — no cast)
RestClient typed = context.getBean("restClient", RestClient.class);

Output:

Bean 'restClient' resolved from container
Singleton instance shared across all lookups: true

Warning: Avoid scattering getBean calls through application code — that re-introduces the tight coupling IoC removes. Reserve it for bootstrap and infrastructure code; everywhere else, inject.

Container responsibilities

In summary, the IoC container is responsible for:

  • Reading bean definitions from annotations, Java config, and auto-configuration.
  • Instantiating beans and resolving constructor/setter dependencies.
  • Managing scope — one shared singleton by default, or other scopes on request.
  • Driving the lifecycle — running initialization and destruction callbacks.
  • Publishing events and exposing infrastructure such as MessageSource.

In This Section

Last updated June 13, 2026
Was this helpful?