Spring Boot
ApplicationContext
ApplicationContext is the central interface of the Spring IoC container. It holds all beans, manages their lifecycle, provides dependency injection, and offers a rich set of enterprise features — event publishing, internationalization, resource loading, and environment abstraction. Understanding ApplicationContext gives you full visibility and control over the Spring container.
What Is ApplicationContext?
ApplicationContext is Spring's central interface for the IoC container. It extends BeanFactory (the basic container) with a rich set of enterprise features. When you call SpringApplication.run(), Spring Boot creates and starts an ApplicationContext — loading your configuration, creating all beans, wiring dependencies, and making everything ready to handle requests.
ApplicationContext provides:
- Bean management — create, configure, assemble, and manage beans
- Dependency injection — resolve and inject all @Autowired dependencies
- Event publishing — publish ApplicationEvents to registered listeners
- Resource loading — load files from classpath, filesystem, or URLs
- Environment abstraction — access properties, profiles, and environment variables
- Internationalization — resolve messages for i18n support
- AOP integration — apply aspects (transactions, caching, security) to beans
- Lifecycle management — start/stop beans in the correct order
BeanFactory is the minimal container — it provides basic DI. ApplicationContext extends BeanFactory with all the enterprise features above. In practice, you always use ApplicationContext in Spring Boot — BeanFactory is rarely used directly.
ApplicationContext Types
Spring provides several ApplicationContext implementations for different environments. Spring Boot selects the right one automatically based on what's on the classpath.
Java
// Spring Boot selects ApplicationContext type automatically:
// 1. AnnotationConfigServletWebServerApplicationContext
// → Used when: spring-boot-starter-web (Tomcat/Jetty) is on classpath
// → Supports: @Controller, @RestController, @RequestMapping
// → Starts: embedded Tomcat, Jetty, or Undertow
// → Most common type in Spring Boot applications
// 2. AnnotationConfigReactiveWebServerApplicationContext
// → Used when: spring-boot-starter-webflux is on classpath (without servlet web)
// → Supports: reactive @RestController, WebFlux, RouterFunction
// → Starts: embedded Netty (non-blocking)
// 3. AnnotationConfigApplicationContext
// → Used when: no web starter on classpath
// → Supports: @Service, @Repository, @Component, @Configuration
// → No embedded server — for batch jobs, CLI apps, message consumers
// Force the context type programmatically:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setWebApplicationType(WebApplicationType.NONE); // force non-web
app.setWebApplicationType(WebApplicationType.SERVLET); // force servlet
app.setWebApplicationType(WebApplicationType.REACTIVE); // force reactive
app.run(args);
}
}
// Non-Boot — create ApplicationContext manually (useful in tests or standalone apps):
// Java config:
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// With component scanning:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("com.example.myapp");
context.refresh();
// Programmatic bean registration:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(UserService.class);
context.registerBean(UserRepository.class);
context.refresh();Accessing the ApplicationContext
In most Spring Boot code, you never need to access the ApplicationContext directly — dependency injection handles everything. When you do need it, there are several clean ways to get it.
Java
// METHOD 1 — Inject directly (cleanest for beans that genuinely need it):
@Service
public class DynamicServiceLocator {
private final ApplicationContext context;
public DynamicServiceLocator(ApplicationContext context) {
this.context = context;
}
// Look up a bean by type at runtime:
public <T> T getService(Class<T> serviceType) {
return context.getBean(serviceType);
}
// Look up a bean by name:
public Object getServiceByName(String name) {
return context.getBean(name);
}
}
// METHOD 2 — ApplicationContextAware interface:
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext ctx) {
SpringContextHolder.context = ctx;
}
// Static accessor — useful for legacy code that can't use injection:
public static ApplicationContext getContext() {
return context;
}
public static <T> T getBean(Class<T> type) {
return context.getBean(type);
}
}
// Use sparingly — breaks IoC principles. Only for bridging Spring into
// legacy code or frameworks that don't support injection.
// METHOD 3 — SpringApplication.run() returns the context:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(Application.class, args);
// Use context directly (rare — mainly for startup scripts or tests):
UserService userService = context.getBean(UserService.class);
System.out.println("Beans loaded: " + context.getBeanDefinitionCount());
// Close the context programmatically (triggers @PreDestroy):
context.close();
}
}
// METHOD 4 — In tests — @SpringBootTest injects it:
@SpringBootTest
class ApplicationContextTest {
@Autowired
private ApplicationContext context;
@Test
void contextContainsExpectedBeans() {
assertThat(context.containsBean("userService")).isTrue();
assertThat(context.getBean(UserService.class)).isNotNull();
}
}Core ApplicationContext Operations
ApplicationContext provides a rich API for inspecting and working with beans, checking the environment, and loading resources. These are the most useful operations.
Java
@Component
public class ContextInspector {
private final ApplicationContext context;
public ContextInspector(ApplicationContext context) {
this.context = context;
}
public void inspect() {
// ── Bean retrieval ────────────────────────────────────────────
// Get a bean by type:
UserService userService = context.getBean(UserService.class);
// Get a bean by name:
Object service = context.getBean("userService");
// Get a bean by name and type (type-safe):
UserService typedService = context.getBean("userService", UserService.class);
// Get all beans of a type (includes subclasses):
Map<String, PaymentService> services = context.getBeansOfType(PaymentService.class);
// {"stripePaymentService": ..., "paypalPaymentService": ...}
// Get beans with a specific annotation:
Map<String, Object> controllers = context.getBeansWithAnnotation(RestController.class);
// ── Bean existence and metadata ───────────────────────────────
boolean exists = context.containsBean("userService"); // by name
boolean isSingleton = context.isSingleton("userService"); // scope check
boolean isPrototype = context.isPrototype("userService");
// Check if a bean is of a specific type:
boolean isType = context.isTypeMatch("userService", UserService.class);
// Get the type of a bean:
Class<?> type = context.getType("userService"); // UserService.class
// List all bean definition names:
String[] allBeans = context.getBeanDefinitionNames();
int beanCount = context.getBeanDefinitionCount();
// ── Environment ───────────────────────────────────────────────
Environment env = context.getEnvironment();
String port = env.getProperty("server.port", "8080");
String[] profiles = env.getActiveProfiles();
boolean isProd = env.acceptsProfiles(Profiles.of("prod"));
// ── Resources ─────────────────────────────────────────────────
Resource classpathResource = context.getResource("classpath:templates/email.html");
Resource fileResource = context.getResource("file:/etc/myapp/config.json");
Resource urlResource = context.getResource("https://api.example.com/config");
try {
String content = new String(classpathResource.getInputStream().readAllBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
// ── Application info ──────────────────────────────────────────
String appName = context.getApplicationName();
String displayName = context.getDisplayName();
long startupDate = context.getStartupDate();
}
}ApplicationContext Events
ApplicationContext publishes events at key lifecycle moments. You can listen to built-in events or publish your own custom events to decouple components.
Java
// Built-in ApplicationContext events in lifecycle order:
// 1. ApplicationStartingEvent — very first event, before anything is prepared
// 2. ApplicationEnvironmentPreparedEvent — environment ready, context not yet created
// 3. ApplicationContextInitializedEvent — context created, beans not loaded
// 4. ApplicationPreparedEvent — context loaded, not yet refreshed
// 5. ContextRefreshedEvent — context refreshed (all beans created)
// 6. ApplicationStartedEvent — context refreshed, before runners execute
// 7. AvailabilityChangeEvent (CORRECT) — application is live
// 8. ApplicationReadyEvent — runners complete, app ready for traffic
// 9. AvailabilityChangeEvent (ACCEPTING_TRAFFIC) — accepting traffic
// On failure: ApplicationFailedEvent
// Listen to lifecycle events with @EventListener:
@Component
@Slf4j
public class LifecycleEventListener {
@EventListener(ApplicationReadyEvent.class)
public void onReady(ApplicationReadyEvent event) {
log.info("Application ready — all beans initialized, runners complete");
// Safe to: make external calls, warm caches, start schedulers
// Runs AFTER CommandLineRunner and ApplicationRunner
}
@EventListener(ContextRefreshedEvent.class)
public void onRefresh(ContextRefreshedEvent event) {
log.info("Context refreshed — beans created, before runners");
}
@EventListener(ContextClosedEvent.class)
public void onClosing(ContextClosedEvent event) {
log.info("Context closing — @PreDestroy methods will run next");
}
@EventListener(ApplicationFailedEvent.class)
public void onFailure(ApplicationFailedEvent event) {
log.error("Startup failed: {}", event.getException().getMessage());
// Send alert, cleanup temp files, etc.
}
}
// CUSTOM EVENTS — publish and listen to your own events:
// Define a custom event:
public class UserRegisteredEvent extends ApplicationEvent {
private final String userId;
private final String email;
public UserRegisteredEvent(Object source, String userId, String email) {
super(source);
this.userId = userId;
this.email = email;
}
public String getUserId() { return userId; }
public String getEmail() { return email; }
}
// Publish a custom event:
@Service
public class UserService {
private final ApplicationEventPublisher eventPublisher;
public UserService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public User createUser(CreateUserRequest request) {
User user = userRepository.save(new User(request));
// Publish event — UserService doesn't know what happens next:
eventPublisher.publishEvent(new UserRegisteredEvent(this, user.getId(), user.getEmail()));
return user;
}
}
// Listen to the custom event:
@Component
public class WelcomeEmailSender {
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
emailService.sendWelcome(event.getEmail());
}
}
@Component
public class AuditLogger {
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
auditService.log("USER_REGISTERED", event.getUserId());
}
}
// Multiple listeners — both run independently
// UserService has zero knowledge of either listener
// Add/remove listeners without touching UserService
// Async event handling — listener runs in a background thread:
@Component
public class AnalyticsTracker {
@EventListener
@Async
public void onUserRegistered(UserRegisteredEvent event) {
// Runs in background thread — doesn't slow down the registration request
analyticsService.trackSignup(event.getUserId());
}
}
// Conditional event listening — only handle if condition is true:
@EventListener(condition = "#event.email.endsWith('@corporate.com')")
public void onCorporateUserRegistered(UserRegisteredEvent event) {
provisioningService.setupCorporateAccount(event.getUserId());
}Environment and Profiles
The Environment abstraction in ApplicationContext provides unified access to properties from all sources and lets you check which profiles are active — fundamental for environment-specific configuration.
Java
@Service
public class EnvironmentAwareService {
private final Environment environment;
public EnvironmentAwareService(Environment environment) {
this.environment = environment;
}
public void showEnvironmentInfo() {
// ── Property access ───────────────────────────────────────────
// Get a property (null if not found):
String dbUrl = environment.getProperty("spring.datasource.url");
// Get with default value:
String port = environment.getProperty("server.port", "8080");
// Get typed property:
Integer maxConnections = environment.getProperty(
"spring.datasource.hikari.maximum-pool-size", Integer.class, 10);
// Required property — throws if missing:
String secret = environment.getRequiredProperty("app.jwt.secret");
// Check if a property exists:
boolean hasMetrics = environment.containsProperty("management.metrics.enabled");
// ── Profile access ────────────────────────────────────────────
// Get active profiles:
String[] active = environment.getActiveProfiles();
// e.g., ["prod", "metrics"]
// Get default profiles (used when no active profiles set):
String[] defaults = environment.getDefaultProfiles();
// Check if a specific profile is active:
boolean isProd = Arrays.asList(active).contains("prod");
// Check with Profiles.of (supports complex expressions):
boolean isProdOrStaging = environment.acceptsProfiles(
Profiles.of("prod | staging")
);
boolean isNotProd = environment.acceptsProfiles(
Profiles.of("!prod")
);
boolean isDevAndMetrics = environment.acceptsProfiles(
Profiles.of("dev & metrics")
);
}
}
// Programmatically set active profile before context starts:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setAdditionalProfiles("metrics"); // add profile programmatically
app.run(args);
}
}
// @Profile on beans — only created when profile is active:
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public DataSource h2DataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
public DataSource mysqlDataSource(DataSourceProperties props) {
return DataSourceBuilder.create()
.url(props.getUrl())
.username(props.getUsername())
.password(props.getPassword())
.build();
}
}Hierarchical ApplicationContext
ApplicationContexts can be arranged in a parent-child hierarchy. Child contexts can see all beans from the parent, but the parent cannot see beans defined in child contexts. Spring MVC uses this pattern internally.
Java
// Parent-child context hierarchy:
// Parent context: infrastructure beans (DataSource, Security, Service layer)
// Child context: web beans (Controllers, ViewResolvers, HandlerMappings)
// Spring MVC (without Boot) traditionally used two contexts:
// Root ApplicationContext (parent) — services, repos, config
// WebApplicationContext (child) — controllers, MVC config
// Spring Boot uses a single context by default — simpler and recommended
// Creating a parent-child relationship manually:
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.register(InfrastructureConfig.class);
parent.refresh();
AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext();
child.setParent(parent);
child.register(WebConfig.class);
child.refresh();
// Child can see parent's beans:
DataSource ds = child.getBean(DataSource.class); // defined in parent — works
// Parent CANNOT see child's beans:
// parent.getBean(UserController.class); // throws NoSuchBeanDefinitionException
// Why this matters for Spring Boot:
// @SpringBootTest loads the full parent+child context as one unified context
// @WebMvcTest loads ONLY the child (web layer) context — faster tests
// @DataJpaTest loads ONLY the persistence context — no web layer
// In tests — check which context is used:
@SpringBootTest
class FullContextTest {
@Autowired ApplicationContext context;
// context is the full application context
// Contains ALL beans: controllers, services, repos, config
}
@WebMvcTest(UserController.class)
class WebLayerTest {
@Autowired ApplicationContext context;
// context is the web application context
// Contains ONLY: UserController, MVC config, security filters
// Does NOT contain: UserService, UserRepository (use @MockBean for these)
}Refreshing and Closing the Context
The ApplicationContext lifecycle has three key operations: refresh (load and wire all beans), close (destroy all beans and release resources), and restart (close then refresh). Understanding these is important for testing, lifecycle management, and advanced use cases.
Java
// Normal Spring Boot startup — context is created, refreshed, and kept open:
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// context is now fully refreshed — all beans created, server started
// Programmatic context close — triggers graceful shutdown:
context.close();
// Sequence:
// 1. ContextClosedEvent published
// 2. Lifecycle beans stopped (SmartLifecycle.stop())
// 3. @PreDestroy methods called (reverse creation order)
// 4. DisposableBean.destroy() called
// 5. Custom destroyMethod called
// 6. Context is now closed — getBean() throws IllegalStateException
// Check if context is active (not closed):
boolean active = context.isActive();
boolean running = context.isRunning();
// Register a shutdown hook — ensures close() is called on JVM shutdown:
context.registerShutdownHook();
// Spring Boot registers this automatically — no need to call manually
// Refresh — reload bean definitions and recreate all beans:
// (Used in dynamic reconfiguration scenarios)
ConfigurableApplicationContext refreshableContext =
(ConfigurableApplicationContext) context;
refreshableContext.refresh();
// WARNING: refresh() recreates all beans — expensive and potentially disruptive
// Spring Boot does NOT call refresh() after initial startup
// In tests — context is shared across test methods in the same class:
@SpringBootTest
class UserServiceTest {
@Autowired UserService userService; // same context instance for all @Test methods
}
// @DirtiesContext — mark context as dirty (triggers recreation for next test):
@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class StatefulTest {
// Each test method gets a fresh context — useful when tests modify beans
}
// Close the context in tests manually (rare):
@SpringBootTest
class ExplicitContextTest {
@Autowired ConfigurableApplicationContext context;
@AfterEach
void cleanup() {
if (someCondition) {
context.close(); // forces recreation for next test
}
}
}ApplicationContext in Tests
Spring Boot's test framework integrates deeply with ApplicationContext — providing sliced contexts for fast tests and the full context for integration tests.
Java
// Full application context — all beans loaded:
@SpringBootTest
@ActiveProfiles("test")
class FullIntegrationTest {
@Autowired private ApplicationContext context;
@Autowired private UserService userService;
@Autowired private UserRepository userRepository;
@Test
void contextLoads() {
assertThat(context.getBeanDefinitionCount()).isGreaterThan(50);
assertThat(context.containsBean("userService")).isTrue();
}
@Test
void activeProfileIsTest() {
assertThat(context.getEnvironment().getActiveProfiles()).contains("test");
}
}
// Sliced context — only web layer:
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired private MockMvc mockMvc;
@Autowired private ApplicationContext context;
@MockBean private UserService userService;
@Test
void contextContainsOnlyWebBeans() {
// UserController is present:
assertThat(context.containsBean("userController")).isTrue();
// UserRepository is NOT present (not loaded by @WebMvcTest):
assertThat(context.containsBean("userRepository")).isFalse();
}
}
// Sliced context — only JPA layer:
@DataJpaTest
class UserRepositoryTest {
@Autowired private ApplicationContext context;
@Autowired private UserRepository userRepository;
@Test
void contextContainsOnlyJpaBeans() {
assertThat(context.containsBean("userRepository")).isTrue();
// UserService is NOT present (not loaded by @DataJpaTest):
assertThat(context.containsBean("userService")).isFalse();
}
}
// Customize the ApplicationContext in tests:
@SpringBootTest
class CustomContextTest {
@TestConfiguration
static class TestConfig {
@Bean
@Primary
public PaymentService mockPaymentService() {
return new MockPaymentService(); // override real payment service
}
}
@Autowired private PaymentService paymentService;
@Test
void usesMockPaymentService() {
assertThat(paymentService).isInstanceOf(MockPaymentService.class);
}
}