Spring IoC Container and ApplicationContext Explained
Introduction
This tutorial explains the Spring IoC container - the part of Spring that creates your objects, connects them and manages them for the life of the application - andApplicationContext, the interface you use to work with it. We will start with what the container does and how it turns configuration into bean definitions and then into objects. We will then create an application context, get beans from it in the different ways Spring offers, and see when the container creates beans. Next we will compare BeanFactory with ApplicationContext and try the extra features of the latter - events and the environment. Lastly, we will look at the errors you get when a bean is missing or ambiguous, the main context implementations, and closing a context properly.(This tutorial continues from What is Spring? Spring Framework vs Spring Boot ExplainedClick to read tutorial: What is Spring? Spring Framework vs Spring Boot Explained, where the container first appears.)
What is the IoC container?
In an ordinary Java program, your code creates the objects it needs and calls them - your code is in control. With Inversion of Control (IoC), that control is handed to a framework: the framework creates the objects, decides when, and passes each object the other objects it depends on. In Spring, the component that does this is the IoC container.The objects the container manages are called beans. A bean is an ordinary Java object - the only thing that makes it a bean is that the container created it and keeps track of it. For each bean the container knows -
- how to create it: which class, which constructor or factory method, and which arguments;
- what it depends on: which other beans to inject into it;
- its scope: whether there is one shared instance or a new one each time (covered in the tutorial on bean scopesClick to read tutorial: Spring Bean Scopes: Singleton, Prototype, Request, Session);
- its lifecycle: what to call after it is created and before it is destroyed (covered in the tutorial on the bean lifecycleClick to read tutorial: Spring Bean Lifecycle: @PostConstruct, @PreDestroy and More).
From configuration to beans
The container works in two phases. First it reads the configuration metadata - your instructions about which beans exist - and turns each one into aBeanDefinition: a recipe holding the class, scope, dependencies and other settings. Then it uses those recipes to create the actual objects. The configuration metadata can come from -
- Annotations on your classes:
@Componentand related annotations, found by component scanning. - Java configuration:
@Configurationclasses with@Beanmethods. - XML files: the original Spring format, still supported but rarely used in new code.
Creating an ApplicationContext and getting beans
The example below declares two beans in a@Configuration class - a GreetingService and a ReportGenerator that uses it - starts an AnnotationConfigApplicationContext, and then looks beans up in four different ways -
Creating an AnnotationConfigApplicationContext and looking up beans
//ContextBasicsApp.java
package com.javabrahman.spring.ioc.basics;
import java.util.Arrays;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
class GreetingService {
String greet(String name) {
return "Hello, " + name;
}
}
class ReportGenerator {
private final GreetingService greetingService;
ReportGenerator(GreetingService greetingService) {
this.greetingService = greetingService;
}
String header(String name) {
return "== Report for " + name + " == " + greetingService.greet(name);
}
}
@Configuration
class AppConfig {
@Bean
GreetingService greetingService() {
return new GreetingService();
}
@Bean
ReportGenerator reportGenerator(GreetingService greetingService) {
return new ReportGenerator(greetingService);
}
}
public class ContextBasicsApp {
public static void main(String[] args) {
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
// 1. By type
ReportGenerator byType = context.getBean(ReportGenerator.class);
System.out.println(byType.header("Harry Major"));
// 2. By name (returns Object, so a cast is needed)
GreetingService byName = (GreetingService) context.getBean("greetingService");
System.out.println(byName.greet("Ethan Hardy"));
// 3. By name and type (no cast)
GreetingService byNameAndType = context.getBean("greetingService", GreetingService.class);
System.out.println("Same instance both times: " + (byName == byNameAndType));
// 4. Asking the container what it holds
System.out.println("Contains 'reportGenerator': " + context.containsBean("reportGenerator"));
System.out.println("Our beans: " + Arrays.stream(context.getBeanDefinitionNames())
.filter(name -> !name.contains(".")).toList());
}
}
}
== Report for Harry Major == Hello, Harry Major Hello, Ethan Hardy Same instance both times: true Contains 'reportGenerator': true Our beans: [appConfig, greetingService, reportGenerator]
Explanation of the code
- Each
@Beanmethod inAppConfigdeclares one bean. The bean's name is the method name, and its type is the method's return type. reportGenerator()takes aGreetingServiceparameter. Spring sees the parameter and passes in thegreetingServicebean - this is dependency injection into a@Beanmethod.new AnnotationConfigApplicationContext(AppConfig.class)reads the configuration class, registers the bean definitions, and creates the beans - all in the constructor call.getBean(Class)is the most common lookup.getBean(String)looks up by name and returnsObject;getBean(String, Class)looks up by name and checks the type, so no cast is needed.- Both name-based lookups returned the same instance: beans are singletons by default, so the container creates one
GreetingServiceand hands out that one object every time. getBeanDefinitionNames()also returns Spring's own internal beans, whose names contain dots, so the example filters them out to show only ours.
getBean() yourself. You call it once, at the edge - in main(), as here - or not at all, as in Spring Boot. Everywhere else, beans receive their dependencies through injection.
When does the container create beans?
AnApplicationContext creates all singleton beans eagerly, while it starts, before your code asks for any of them. A bean marked @Lazy is created only when it is first requested. The constructors below print a line so that we can see the order -
Eager and lazy creation of singleton beans
//EagerVsLazyApp.java
package com.javabrahman.spring.ioc.lazy;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
class CacheWarmer {
CacheWarmer() {
System.out.println("CacheWarmer created");
}
}
class PdfExporter {
PdfExporter() {
System.out.println("PdfExporter created");
}
}
@Configuration
class AppConfig {
@Bean
CacheWarmer cacheWarmer() {
return new CacheWarmer();
}
@Bean
@Lazy
PdfExporter pdfExporter() {
return new PdfExporter();
}
}
public class EagerVsLazyApp {
public static void main(String[] args) {
System.out.println("Starting the context");
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
System.out.println("Context started");
context.getBean(PdfExporter.class);
System.out.println("PdfExporter requested");
}
}
}
Starting the context CacheWarmer created Context started PdfExporter created PdfExporter requested
Explanation of the code
CacheWarmerwas created during startup, before "Context started" - eager creation.PdfExporter, marked@Lazy, was created only whengetBean(PdfExporter.class)asked for it.- Eager creation is the safer default: if a bean cannot be created - a missing dependency, a bad setting - the application fails at startup, not hours later when some request first needs that bean. Use
@Lazyonly for beans that are expensive and rarely needed.
BeanFactory vs ApplicationContext
Spring has two container interfaces.BeanFactory is the basic one: it holds bean definitions and creates beans on request. ApplicationContext extends BeanFactory and adds what an application needs around it. You can use a plain BeanFactory - DefaultListableBeanFactory is the implementation every application context uses internally - but it does much less on its own -
A plain DefaultListableBeanFactory creates beans only on request
//BeanFactoryApp.java
package com.javabrahman.spring.ioc.factory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
class AuditLog {
AuditLog() {
System.out.println("AuditLog created");
}
}
public class BeanFactoryApp {
public static void main(String[] args) {
var beanFactory = new DefaultListableBeanFactory();
// Register a bean definition by hand - the recipe, not the object
beanFactory.registerBeanDefinition("auditLog", new RootBeanDefinition(AuditLog.class));
System.out.println("Definition registered, beans so far: " + beanFactory.getSingletonCount());
beanFactory.getBean("auditLog");
System.out.println("After getBean(), beans so far: " + beanFactory.getSingletonCount());
}
}
Definition registered, beans so far: 0 AuditLog created After getBean(), beans so far: 1
Explanation of the code
registerBeanDefinition()adds aRootBeanDefinition- the recipe for anAuditLogbean. No object exists yet.- The plain bean factory creates
AuditLogonly atgetBean(). It has no startup phase that creates singletons in advance. - A plain
BeanFactoryalso does not process annotations, publish events or resolve messages unless you set those up by hand. AnApplicationContextdoes all of that out of the box.
ApplicationContext adds over BeanFactory -
- Eager creation of singletons at startup, as seen above.
- Automatic annotation processing:
@Autowired,@Value,@PostConstructand the rest work without extra setup. - Application events: publishing events and delivering them to listener beans.
- The
Environment: profiles and property sources such as system properties, environment variables and property files. - Messages: internationalized text through a
MessageSource. - Resources: loading files from the classpath, the file system or URLs through one API.
ApplicationContext in applications. BeanFactory matters mainly because the Spring documentation and error messages mention it, and because it is the interface that declares getBean().
ApplicationContext features: events and the environment
Two of those extras are used constantly in real applications. Events let one bean announce that something happened without knowing who reacts - a built-in implementation of the Observer design patternClick to read: Observer design pattern. The environment gives any bean access to configuration values -Publishing an application event and reading the environment
//ContextFeaturesApp.java
package com.javabrahman.spring.ioc.features;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
// An event is any object; a record keeps it short
record OrderPlaced(String customer, double amount) {
}
@Component
class OrderService {
private final ApplicationEventPublisher publisher;
OrderService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
void placeOrder(String customer, double amount) {
System.out.println("Order placed by " + customer);
publisher.publishEvent(new OrderPlaced(customer, amount));
}
}
@Component
class LoyaltyPoints {
@EventListener
void on(OrderPlaced event) {
System.out.println("Loyalty points for " + event.customer() + ": " + (int) event.amount());
}
}
@Component
class StartupLogger {
@EventListener
void on(ContextRefreshedEvent event) {
System.out.println("Context refreshed - all singleton beans are ready");
}
}
@Configuration
@ComponentScan
class AppConfig {
}
public class ContextFeaturesApp {
public static void main(String[] args) {
System.setProperty("shop.name", "JavaBrahman Books");
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
context.getBean(OrderService.class).placeOrder("Nancy Smith", 120.0);
System.out.println("shop.name = " + context.getEnvironment().getProperty("shop.name"));
System.out.println("missing.key with default = "
+ context.getEnvironment().getProperty("missing.key", "default value"));
}
}
}
Context refreshed - all singleton beans are ready Order placed by Nancy Smith Loyalty points for Nancy Smith: 120 shop.name = JavaBrahman Books missing.key with default = default value
Explanation of the code
ApplicationEventPublisheris injected like any bean - the application context itself implements it.OrderServicepublishes anOrderPlacedevent and does not know thatLoyaltyPointsexists.- A method annotated
@EventListenerreceives every published event whose type matches its parameter. By default listeners run synchronously, in the publisher's thread, beforepublishEvent()returns. - Spring publishes its own events too.
ContextRefreshedEventarrives once the context has created all singleton beans, soStartupLoggerprinted first. getEnvironment().getProperty()reads a value from the property sources - here the JVM system property set inmain(). A second argument gives a default for a missing key. In beans, the usual way to read properties is@Value, covered in the tutorial on SpEL and @ValueClick to read tutorial: Spring Expression Language (SpEL) and @Value Explained.
When the container cannot find the right bean
getBean() by type needs exactly one matching bean. With none, or with more than one, it throws an exception that names the problem -
NoSuchBeanDefinitionException and NoUniqueBeanDefinitionException
//LookupErrorsApp.java
package com.javabrahman.spring.ioc.errors;
import org.springframework.beans.BeansException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
interface Notifier {
}
class EmailNotifier implements Notifier {
}
class SmsNotifier implements Notifier {
}
class InvoicePrinter {
}
@Configuration
class AppConfig {
@Bean
EmailNotifier emailNotifier() {
return new EmailNotifier();
}
@Bean
SmsNotifier smsNotifier() {
return new SmsNotifier();
}
}
public class LookupErrorsApp {
public static void main(String[] args) {
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
lookUp(context, InvoicePrinter.class);
lookUp(context, Notifier.class);
}
}
static void lookUp(AnnotationConfigApplicationContext context, Class<?> type) {
try {
context.getBean(type);
} catch (BeansException e) {
System.out.println(e.getClass().getSimpleName() + ":\n " + e.getMessage());
}
}
}
NoSuchBeanDefinitionException: No qualifying bean of type 'com.javabrahman.spring.ioc.errors.InvoicePrinter' available NoUniqueBeanDefinitionException: No qualifying bean of type 'com.javabrahman.spring.ioc.errors.Notifier' available: expected single matching bean but found 2: emailNotifier,smsNotifier
Explanation of the code
InvoicePrinteris not a bean at all, so the lookup fails withNoSuchBeanDefinitionException.- Both
EmailNotifierandSmsNotifierimplementNotifier, so a lookup by the interface type finds two candidates and fails withNoUniqueBeanDefinitionException, which lists them. - The same two errors happen during injection, at startup, when a constructor asks for a type with no bean or with several. The tutorial on @Qualifier, @Primary and conditional beansClick to read tutorial: Spring @Qualifier, @Primary and Conditional Beans shows how to tell Spring which one to use.
ApplicationContext implementations
ApplicationContext is an interface with several implementations. The ones you are likely to meet -
AnnotationConfigApplicationContext: reads@Configurationclasses and scans packages. Used in this series for plain Spring Framework examples.GenericApplicationContext: a flexible context you fill by registering beans in code, including with lambdas.ClassPathXmlApplicationContextandFileSystemXmlApplicationContext: read XML configuration, common in older applications.- Web server application contexts: servlet and reactive variants that Spring Boot creates for web applications; besides holding the beans, they start the embedded web server.
SpringApplication.run() chooses and creates the right context for you, and returns it.
Closing the context
Closing a context destroys its singleton beans - it calls their destroy callbacks so that they can release connections, threads and files - and publishes aContextClosedEvent. The examples in this tutorial close it with try-with-resources, since ConfigurableApplicationContext is AutoCloseable. In a long-running application, call context.registerShutdownHook() instead, so that the context closes when the JVM shuts down. Spring Boot registers this hook for you.
Summary
In this tutorial we first saw what the IoC container is and what it knows about each bean, and how it turns configuration metadata into bean definitions and then into objects. We created anAnnotationConfigApplicationContext and looked up beans by type, by name, and by both. We saw that an application context creates singleton beans eagerly at startup, and that @Lazy postpones creation until first use. We then compared BeanFactory with ApplicationContext, and tried two of the context's extra features - application events and the environment. Lastly, we saw the exceptions for missing and ambiguous beans, the main context implementations, and how to close a context. The next tutorial covers the ways to declare beans: @Component, @Bean and @ConfigurationClick to read tutorial: Spring Beans: @Component, @Bean and @Configuration.Spring Tutorials
What is Spring? Spring Framework vs Spring Boot ExplainedClick to read tutorial: What is Spring? Spring Framework vs Spring Boot Explained Spring Beans: @Component, @Bean and @ConfigurationClick to read tutorial: Spring Beans: @Component, @Bean and @Configuration Spring Dependency Injection: Constructor vs Setter vs FieldClick to read tutorial: Spring Dependency Injection: Constructor vs Setter vs Field Spring Bean Scopes: Singleton, Prototype, Request, SessionClick to read tutorial: Spring Bean Scopes: Singleton, Prototype, Request, Session Spring Bean Lifecycle: @PostConstruct, @PreDestroy and MoreClick to read tutorial: Spring Bean Lifecycle: @PostConstruct, @PreDestroy and More Spring @Qualifier, @Primary and Conditional BeansClick to read tutorial: Spring @Qualifier, @Primary and Conditional Beans Spring Expression Language (SpEL) and @Value ExplainedClick to read tutorial: Spring Expression Language (SpEL) and @Value Explained
What is Spring? Spring Framework vs Spring Boot ExplainedClick to read tutorial: What is Spring? Spring Framework vs Spring Boot Explained Spring Beans: @Component, @Bean and @ConfigurationClick to read tutorial: Spring Beans: @Component, @Bean and @Configuration Spring Dependency Injection: Constructor vs Setter vs FieldClick to read tutorial: Spring Dependency Injection: Constructor vs Setter vs Field Spring Bean Scopes: Singleton, Prototype, Request, SessionClick to read tutorial: Spring Bean Scopes: Singleton, Prototype, Request, Session Spring Bean Lifecycle: @PostConstruct, @PreDestroy and MoreClick to read tutorial: Spring Bean Lifecycle: @PostConstruct, @PreDestroy and More Spring @Qualifier, @Primary and Conditional BeansClick to read tutorial: Spring @Qualifier, @Primary and Conditional Beans Spring Expression Language (SpEL) and @Value ExplainedClick to read tutorial: Spring Expression Language (SpEL) and @Value Explained