LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 719 min read

The Spring IoC Container

Learn how the ApplicationContext reads configuration, creates beans, and manages their lifecycle from startup to shutdown.

Introduction

In Lesson 6 you learned the principle of Inversion of Control. Now let's look at the actual object responsible for implementing it in Spring: the IoC container, most commonly accessed through the ApplicationContext interface. This lesson explains what the container does, how it reads your configuration, and the lifecycle every bean passes through.

BeanFactory vs ApplicationContext

Spring actually has two related container interfaces. BeanFactory is the most basic container, providing simple bean creation and wiring. ApplicationContext is a more feature-rich sub-interface built on top of BeanFactory, adding things like event publishing, internationalization support, and easier integration with annotation-based configuration. In practice, almost every Spring application uses ApplicationContext.

FeatureBeanFactoryApplicationContext
Basic bean creation and DIYesYes
Annotation-based configuration supportLimitedFull support
Event publishingNoYes
Typical usage in real applicationsRare, low-levelStandard choice

Creating an ApplicationContext

You already created an ApplicationContext in Lesson 1 using AnnotationConfigApplicationContext, which is the standard implementation for Java-based configuration.

Creating the container
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// The container is now fully initialized:
// every @Component-annotated class has been scanned,
// created, and wired together.
Greeter greeter = context.getBean(Greeter.class);
System.out.println(greeter.greet("World"));
}
}
Console Output

Click Run to see what this code prints.

How the Container Reads Configuration

When AnnotationConfigApplicationContext starts, it processes the @Configuration class you passed in, follows any @ComponentScan instructions to discover classes annotated with @Component (and its specializations like @Service and @Repository), and builds an internal registry mapping bean names to bean definitions — metadata describing how to construct each bean, including its dependencies.

AppConfig.java
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.programinds.demo")
public class AppConfig {
}

Only after the container has resolved this registry does it actually instantiate the beans, resolving each dependency graph in the correct order so that every dependency exists before the bean that needs it is created.

The Bean Lifecycle

  • Instantiation — the container creates the bean object (usually via its constructor).
  • Populate properties — any remaining dependencies are injected (for setter or field injection).
  • Initialization — any @PostConstruct method or InitializingBean callback runs.
  • Ready for use — the bean is available via getBean() and normal application use.
  • Destruction — on container shutdown, any @PreDestroy method or DisposableBean callback runs.

Lifecycle Callbacks in Code

A bean with lifecycle hooks
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class ConnectionPool {
@PostConstruct
public void init() {
System.out.println("ConnectionPool: opening connections...");
}
@PreDestroy
public void cleanup() {
System.out.println("ConnectionPool: closing connections...");
}
}
Triggering shutdown cleanly
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// ... use the application ...
context.close(); // triggers @PreDestroy on all singleton beans
}
}
Console Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Forgetting to call context.close() (or use try-with-resources) in a standalone app, so @PreDestroy callbacks never run.
  • Assuming @PostConstruct runs during the constructor — it actually runs after all dependencies have been injected.
  • Confusing BeanFactory and ApplicationContext and reaching for the lower-level BeanFactory unnecessarily.

Best Practices

  • Use AnnotationConfigApplicationContext (or Spring Boot's auto-configured context) for all new projects.
  • Put setup logic that depends on injected fields in @PostConstruct, not the constructor.
  • Always close the context explicitly (or let a framework like Spring Boot manage it) so destruction callbacks fire.

Frequently Asked Questions

A bean definition is metadata describing how to create a bean (its class, dependencies, scope); a bean instance is the actual object created from that definition.

Yes, though it is uncommon. Most applications use a single ApplicationContext for the whole application lifecycle.

On modern Java (Jakarta EE 9+), it comes from the jakarta.annotation-api, which Spring Boot includes automatically; in plain Spring projects you may need to add it explicitly.

Key Takeaways

  • ApplicationContext is the standard, feature-rich Spring IoC container interface used in real applications.
  • The container reads @Configuration and @ComponentScan to build a registry of bean definitions before creating any beans.
  • Every bean passes through instantiation, dependency injection, initialization, and eventually destruction.
  • @PostConstruct and @PreDestroy let you hook into the bean lifecycle.

Summary

The ApplicationContext is the engine that makes Inversion of Control real: it reads your configuration, builds bean definitions, and manages every bean's life from creation to destruction. With the container itself explained, we can now focus squarely on Dependency Injection — the technique the container uses to wire beans together.

Next Lesson →

Dependency Injection Basics