Hook: Interviewers love this question because it checks whether you know Spring’s core container or just the surface-level annotations.
Question: BeanFactory vs ApplicationContext.
Answer: BeanFactory is Spring’s basic IoC container: it stores bean definitions, creates beans, and hands them out with getBean(). ApplicationContext builds on top of it and adds richer features such as event publishing, message resolution, resource loading, and environment access. In Spring Boot, you usually work with ApplicationContext because it gives you the full application lifecycle and the extra services most real apps need.
Interview-Ready Answer: "I think of BeanFactory as the engine that knows how to create and wire beans, while ApplicationContext is the full container around it. ApplicationContext extends BeanFactory, so it can do everything BeanFactory can, but it also adds things like events, internationalization, resource loading, and the normal Spring Boot startup lifecycle. In practice, Spring Boot uses ApplicationContext by default because it eagerly creates non-lazy singleton beans at refresh time and gives you a much richer programming model."
Detailed Explanation: The easiest way to remember this is: BeanFactory is the core container, and ApplicationContext is the enterprise-friendly wrapper around it. Both know how to create beans and resolve dependencies, but only the context gives you the wider application services that make Spring Boot feel powerful instead of just usable.
| Aspect | BeanFactory | ApplicationContext |
|---|---|---|
| Role | Core container | Full container |
| Bean creation | On demand | Non-lazy singletons on refresh |
| Extra services | Beans only | Events, i18n, resources, env |
| Typical use | Framework internals | Spring apps, Boot |
| Footprint | Smaller | Richer startup |
ApplicationContext implementation, such as AnnotationConfigServletWebServerApplicationContext for web apps or AnnotationConfigApplicationContext for non-web apps. This is a ConfigurableApplicationContext, which means it can be refreshed and closed.@Lazy means Spring waits until the bean is actually requested.@EventListener works naturally in an ApplicationContext, while plain BeanFactory does not offer that API.ApplicationContext for almost all Spring Boot applications. It gives you the full lifecycle, auto-configuration, events, property resolution, resource loading, and message bundles.BeanFactory when you are writing framework code, a very small embedded container, or a custom setup where you want the bare minimum.Bean lookup is usually very fast because Spring keeps bean metadata in maps, so the average lookup feels like O(1). Type-based lookup can do more work because Spring may need to filter candidates, resolve aliases, and choose a primary bean. The real cost is usually not lookup; it is bean construction, proxy creation, database startup, and initialization callbacks.
A common interview detail: ApplicationContext is not “eager for everything.” It eagerly creates non-lazy singleton beans at refresh time, but @Lazy beans and prototype beans are still created on demand. So the right mental model is “context does a startup pass,” not “context creates every object immediately.”
For numbers: in a typical Boot service with 100-300 beans, bean lookup itself is usually microseconds to low milliseconds, while startup time is dominated by heavyweight beans such as JPA, Flyway, HTTP clients, or connection pools. If a bean opens a DB connection or warms a cache, that one bean can cost far more than the container lookup around it.
BeanFactory is the minimal bean engine. ApplicationContext is the full-featured app container built on top of that engine.
Real-World Story: Imagine a checkout service in an e-commerce system. At startup, it needs to preload exchange rates, connect to Redis, and listen for a custom warmup event before serving traffic. That is a natural fit for ApplicationContext, because it can publish and consume events and run the normal startup lifecycle.
Now imagine a developer thinks, “BeanFactory is lighter, so let’s use that everywhere.” The warmup listener never fires, the cache is not preloaded, and the first customer request has to create expensive objects on the fly. In logs you might see missing event messages, slow first-request latency, or even NoSuchBeanDefinitionException if the code was relying on context-managed wiring that was not set up correctly.
The user-visible symptom is brutal: the app looks healthy after deploy, but the first few checkouts are slow or fail. A p95 latency that should be around 120 ms suddenly jumps to several seconds because the expensive setup moved from startup time into the middle of a live request. That is exactly why Spring Boot prefers a rich ApplicationContext instead of a bare-bones bean factory.
package com.example.demo;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@SpringBootApplication
public class BeanFactoryVsApplicationContextApplication {
public static void main(String[] args) {
SpringApplication.run(BeanFactoryVsApplicationContextApplication.class, args);
}
@Bean
@Lazy
ExpensiveClient expensiveClient() {
// @Lazy means Spring waits until someone asks for this bean.
return new ExpensiveClient();
}
@Bean
CommandLineRunner demo(ApplicationContext applicationContext, BeanFactory beanFactory) {
return args -> {
System.out.println("=== Container types ===");
System.out.println("ApplicationContext: " + applicationContext.getClass().getName());
System.out.println("BeanFactory: " + beanFactory.getClass().getName());
System.out.println();
System.out.println("=== BeanFactory can resolve beans ===");
System.out.println("eagerService exists? " + beanFactory.containsBean("eagerService"));
System.out.println("Eager bean type: " + beanFactory.getBean(EagerService.class).getClass().getSimpleName());
System.out.println();
System.out.println("=== Lazy bean creation ===");
// This line triggers construction of the lazy bean for the first time.
ExpensiveClient client = applicationContext.getBean(ExpensiveClient.class);
System.out.println("Lazy bean says: " + client.call());
System.out.println();
System.out.println("=== Failure path ===");
try {
applicationContext.getBean(String.class);
} catch (NoSuchBeanDefinitionException ex) {
System.out.println("Missing bean handled cleanly: " + ex.getClass().getSimpleName());
}
System.out.println();
System.out.println("=== ApplicationContext-only feature ===");
// BeanFactory does not offer event publishing on its API, but ApplicationContext does.
applicationContext.publishEvent(new DemoEvent("Warmup finished"));
};
}
}
@Component
class EagerService {
EagerService() {
// This prints during context refresh because non-lazy singletons are created eagerly.
System.out.println("EagerService constructed during context refresh");
}
}
@Component
class StartupListener {
@EventListener
public void onContextRefreshed(ContextRefreshedEvent event) {
System.out.println("ContextRefreshedEvent received");
}
@EventListener
public void onDemoEvent(DemoEvent event) {
System.out.println("Custom event received: " + event.message());
}
}
record DemoEvent(String message) {
}
class ExpensiveClient {
ExpensiveClient() {
System.out.println("ExpensiveClient constructed only when requested");
}
String call() {
return "remote-call-ok";
}
}Follow-up & Tricky Questions:
Common Mistakes:
Memory Hook: BeanFactory = kitchen stove. It cooks the beans. ApplicationContext = full restaurant. It cooks, announces orders, translates the menu, and handles the whole dining experience.
Cheat Sheet:
Practice Tasks:
@Lazy bean and print a line in its constructor. Confirm it is created only when first requested.@EventListener method and publish a custom event from a CommandLineRunner.AnnotationConfigApplicationContext and compare what you can do there versus a plain DefaultListableBeanFactory.