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. This entry covers what it is, the context types Spring Boot creates, what happens during startup (refresh), how to access it safely, its events, Environment and profiles, context hierarchies, closing and shutdown, testing, and the most common errors.
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. Two differences matter in practice. First, an ApplicationContext creates all non-lazy singleton beans at startup, so configuration mistakes (a missing bean, a circular dependency) fail the application immediately instead of at the first request. Second, it detects and applies BeanPostProcessor and BeanFactoryPostProcessor beans automatically, which is how @Autowired, @Transactional, @Value and AOP proxies work. In Spring Boot you always use ApplicationContext — 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 is on the classpath. You can override the choice with setWebApplicationType; the three calls below are alternatives (only the last call before run() would take effect), not a sequence.
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 (choose ONE of the three):
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setWebApplicationType(WebApplicationType.NONE); // option A: non-web
// app.setWebApplicationType(WebApplicationType.SERVLET); // option B: servlet
// app.setWebApplicationType(WebApplicationType.REACTIVE); // option C: reactive
app.run(args);
}
}
// Or in application.properties: spring.main.web-application-type=none
// Non-Boot — create ApplicationContext manually (useful in tests or standalone apps):
// Option A — Java config class:
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// Option B — component scanning:
AnnotationConfigApplicationContext scanned = new AnnotationConfigApplicationContext();
scanned.scan("com.example.myapp");
scanned.refresh();
// Option C — programmatic bean registration (concrete classes, not interfaces):
AnnotationConfigApplicationContext manual = new AnnotationConfigApplicationContext();
manual.registerBean(UserService.class);
manual.registerBean(EmailService.class);
manual.refresh();What Happens During refresh()
Starting the context is a fixed sequence, implemented in AbstractApplicationContext.refresh(). Knowing the order explains why some things are possible at certain moments and not at others: for example, a BeanFactoryPostProcessor runs before ordinary beans exist, a BeanPostProcessor wraps beans (creating proxies for @Transactional) as they are created, and the embedded web server starts only near the end. Spring Boot's SpringApplication.run() wraps this refresh with environment preparation before it and runner execution after it.
Java
// SpringApplication.run() — the big picture:
// 1. Publish ApplicationStartingEvent
// 2. Prepare the Environment (properties, profiles)
// 3. Create the ApplicationContext (type chosen from the classpath)
// 4. Load bean definitions (your @SpringBootApplication class, component scan)
// 5. context.refresh() ← the steps below
// 6. Run CommandLineRunner / ApplicationRunner beans
// 7. Publish ApplicationReadyEvent
// AbstractApplicationContext.refresh() in order:
// prepareRefresh() – validate required properties, init state
// obtainFreshBeanFactory() – create the bean factory
// prepareBeanFactory() – register built-in beans (Environment, etc.)
// invokeBeanFactoryPostProcessors() – process @Configuration classes, @ComponentScan,
// @Bean definitions, property placeholders
// registerBeanPostProcessors() – register processors for @Autowired, AOP, etc.
// initMessageSource() – i18n
// initApplicationEventMulticaster() – event publishing infrastructure
// onRefresh() – web contexts: create the embedded server
// registerListeners() – register ApplicationListener beans
// finishBeanFactoryInitialization() – instantiate ALL non-lazy singletons
// finishRefresh() – publish ContextRefreshedEvent, start the
// web server and SmartLifecycle beans
// Practical consequences:
// • A bean definition error fails startup in finishBeanFactoryInitialization().
// • @Lazy beans (or spring.main.lazy-initialization=true) skip that step and are
// created on first use, which speeds startup but hides errors until then.
// • A BeanFactoryPostProcessor @Bean method should be static, because it runs
// before the configuration class itself can be instantiated normally.Accessing the ApplicationContext
In most Spring Boot code, you never need to access the ApplicationContext directly — dependency injection handles everything. Looking beans up from the context by hand is the service locator pattern: it hides a class's real dependencies and makes it harder to test. When you only need "all implementations of an interface" or "an optional or lazily created bean", inject a List, a Map or an ObjectProvider instead. When you do need the context itself, there are several clean ways to get it.
Java
// PREFERRED — inject what you need instead of looking it up:
@Service
public class PaymentRouter {
private final Map<String, PaymentService> servicesByName; // all PaymentService beans
private final ObjectProvider<AuditService> auditProvider; // optional / lazy bean
public PaymentRouter(Map<String, PaymentService> servicesByName,
ObjectProvider<AuditService> auditProvider) {
this.servicesByName = servicesByName;
this.auditProvider = auditProvider;
}
public PaymentService forProvider(String name) {
return servicesByName.get(name); // e.g. "stripePaymentService"
}
public void audit(String event) {
auditProvider.ifAvailable(a -> a.log(event)); // no error if the bean is absent
}
}
// METHOD 1 — Inject directly (for beans that genuinely need the context):
@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. Note that getBeansOfType and getBeansWithAnnotation can instantiate beans that have not been created yet (lazy singletons), and getBeansOfType does not include beans from a parent context.
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(),
StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(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. Two rules are easy to miss. First, the earliest Spring Boot events (ApplicationStartingEvent, ApplicationEnvironmentPreparedEvent, ApplicationContextInitializedEvent, ApplicationPreparedEvent) are published before your @Component beans exist, so an @EventListener method cannot receive them; register an ApplicationListener on the SpringApplication (or in META-INF/spring.factories) instead. ApplicationFailedEvent is the same: a bean-based listener cannot be relied on, so use SpringApplication.addListeners for failure handling. Second, ordinary listeners run synchronously in the publishing thread: a slow listener slows the publisher, and an exception in a listener propagates to it. @Async makes a listener run in the background (it needs @EnableAsync) but then its exceptions are not visible to the caller, and it no longer shares the caller's transaction. For work that must happen only after a database commit, use @TransactionalEventListener.
Java
// Built-in events in the order Spring Boot publishes them:
// 1. ApplicationStartingEvent — very first, nothing prepared yet
// 2. ApplicationEnvironmentPreparedEvent — Environment ready, context not created
// 3. ApplicationContextInitializedEvent — context created, bean definitions not loaded
// 4. ApplicationPreparedEvent — bean definitions loaded, not yet refreshed
// 5. ContextRefreshedEvent — context refreshed (all singletons created)
// 6. WebServerInitializedEvent — web apps: embedded server started
// 7. ApplicationStartedEvent — context refreshed, runners NOT yet executed
// 8. AvailabilityChangeEvent (LivenessState.CORRECT) — application is live
// ... CommandLineRunner / ApplicationRunner beans execute here ...
// 9. ApplicationReadyEvent — runners complete, ready for traffic
// 10. AvailabilityChangeEvent (ReadinessState.ACCEPTING_TRAFFIC)
// On failure: ApplicationFailedEvent
// Listen to lifecycle events that occur after the context is ready:
@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");
// Can fire more than once if there are parent/child contexts
// (for example a separate management-port context)
}
@EventListener(ContextClosedEvent.class)
public void onClosing(ContextClosedEvent event) {
log.info("Context closing — @PreDestroy methods will run next");
}
}
// Early events and failures need a listener registered on SpringApplication:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.addListeners((ApplicationListener<ApplicationFailedEvent>) event ->
System.err.println("Startup failed: " + event.getException().getMessage()));
app.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) event ->
System.out.println("Environment ready, profiles: "
+ Arrays.toString(event.getEnvironment().getActiveProfiles())));
app.run(args);
}
}
// CUSTOM EVENTS — any object can be an event (no ApplicationEvent needed):
// Define a custom event as a record:
public record UserRegisteredEvent(String userId, String email) { }
// Publish a custom event:
@Service
public class UserService {
private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;
public UserService(UserRepository userRepository,
ApplicationEventPublisher eventPublisher) {
this.userRepository = userRepository;
this.eventPublisher = eventPublisher;
}
@Transactional
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(user.getId(), user.getEmail()));
return user;
}
}
// Listen to the custom event:
@Component
@RequiredArgsConstructor
public class WelcomeEmailSender {
private final EmailService emailService;
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
emailService.sendWelcome(event.email());
}
}
@Component
@RequiredArgsConstructor
public class AuditLogger {
private final AuditService auditService;
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
auditService.log("USER_REGISTERED", event.userId());
}
}
// Multiple listeners — both run independently (in the publisher's thread)
// UserService has zero knowledge of either listener
// Add/remove listeners without touching UserService
// Async event handling — listener runs in a background thread.
// Requires @EnableAsync on a configuration class:
@Configuration
@EnableAsync
class AsyncConfig { }
@Component
@RequiredArgsConstructor
public class AnalyticsTracker {
private final AnalyticsService analyticsService;
@EventListener
@Async
public void onUserRegistered(UserRegisteredEvent event) {
// Runs in a background thread — doesn't slow down the registration request.
// Exceptions here are NOT seen by the publisher; handle/log them yourself.
analyticsService.trackSignup(event.userId());
}
}
// Run only after the surrounding transaction COMMITS (not if it rolls back):
@Component
@RequiredArgsConstructor
public class CommittedEventListener {
private final EmailService emailService;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onUserRegistered(UserRegisteredEvent event) {
emailService.sendWelcome(event.email());
}
}
// Conditional event listening — only handle if the SpEL condition is true:
@EventListener(condition = "#event.email().endsWith('@corporate.com')")
public void onCorporateUserRegistered(UserRegisteredEvent event) {
provisioningService.setupCorporateAccount(event.userId());
}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. When the same property is defined in several places, the higher source in Spring Boot's ordering wins: command-line arguments override environment variables, which override profile-specific files such as application-prod.yml, which override application.yml, which override defaults in code.
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);
}
}
// Activate profiles from outside the code (highest-priority sources first):
// java -jar app.jar --spring.profiles.active=prod
// SPRING_PROFILES_ACTIVE=prod java -jar app.jar
// spring.profiles.active=prod (in application.properties)
// @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 {
// Defining your own DataSource bean turns off Spring Boot's DataSource
// auto-configuration (including spring.datasource.hikari.* settings).
// Often it is simpler to keep the auto-configured DataSource and only
// change spring.datasource.* properties per profile.
@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 without Boot traditionally used this pattern, and Spring Boot still creates a child context in a few cases, for example when Actuator runs on a separate management port. Note that a plain getBean lookup in a child searches the parent too, but getBeansOfType does not include parent beans unless you use BeanFactoryUtils.beansOfTypeIncludingAncestors. Test slices such as @WebMvcTest are not parent/child hierarchies: each test loads one single context with a restricted set of auto-configurations.
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
// Include parent beans when collecting by type:
Map<String, PaymentService> all =
BeanFactoryUtils.beansOfTypeIncludingAncestors(child, PaymentService.class);
// Build a hierarchy with Spring Boot's builder:
new SpringApplicationBuilder(ParentConfig.class)
.child(ChildConfig.class)
.run(args);
// In tests, each of these loads ONE context (not a parent/child pair):
// @SpringBootTest → the full application context (all beans)
// @WebMvcTest → a context with only the web layer (controllers, MVC
// config, security filters); services must be mocked
// @DataJpaTest → a context with only the persistence layerRefreshing and Closing the Context
The ApplicationContext lifecycle has two key operations: refresh (load and wire all beans) and close (destroy all beans and release resources). Spring Boot's contexts can be refreshed only once: calling refresh() again on a GenericApplicationContext, which all Boot contexts are, throws an IllegalStateException. To change configuration at runtime, use a mechanism designed for it such as @RefreshScope (Spring Cloud) or a rolling restart; to restart in-process during development, Spring Boot DevTools creates a new context for you. Understanding the shutdown sequence matters for graceful shutdown, resource cleanup and tests.
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()), including the web server
// (with server.shutdown=graceful it waits for in-flight requests)
// 3. @PreDestroy methods called (reverse creation order)
// 4. DisposableBean.destroy() called
// 5. Custom destroyMethod called
// 6. Context is now closed — getBean() throws IllegalStateException
// Check the context state:
boolean active = context.isActive(); // refreshed and not yet closed
boolean running = context.isRunning(); // lifecycle beans started
// Register a shutdown hook — ensures close() is called on JVM shutdown:
context.registerShutdownHook();
// Spring Boot registers this automatically — no need to call manually
// Refresh happens exactly once for Spring Boot contexts:
// context.refresh(); // ← throws IllegalStateException:
// // GenericApplicationContext does not support
// // multiple refresh attempts
// Graceful shutdown settings:
// server.shutdown=graceful
// spring.lifecycle.timeout-per-shutdown-phase=30s
// In tests — the context is cached and shared between test classes
// that have the same configuration (profiles, properties, mock beans, ...):
@SpringBootTest
class UserServiceTest {
@Autowired UserService userService; // same cached context across test classes
}
// @DirtiesContext — mark context as dirty (it is closed and rebuilt for the 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,
// but slow: use it sparingly, because it defeats context caching.
}
// Tip: every distinct combination of @ActiveProfiles, @TestPropertySource,
// @MockitoBean and @TestConfiguration creates a NEW cached context. Keep test
// configuration consistent across test classes to keep the suite fast.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. For testing configuration and auto-configuration in isolation (conditions, property binding, which beans get created), ApplicationContextRunner starts a lightweight context per test without @SpringBootTest. In the examples, @MockitoBean replaces the old @MockBean, which was deprecated in Spring Boot 3.4 and removed in Spring Boot 4.
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.containsBean("userService")).isTrue();
assertThat(context.getBean(UserService.class)).isNotNull();
}
@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;
@MockitoBean private UserService userService; // replaces the removed @MockBean
@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);
}
}
// Lightweight context for testing configuration and conditions:
class GreetingAutoConfigurationTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withUserConfiguration(GreetingConfig.class);
@Test
void createsBean_whenPropertyEnabled() {
runner.withPropertyValues("greeting.enabled=true")
.run(context -> assertThat(context).hasSingleBean(GreetingService.class));
}
@Test
void backsOff_whenPropertyDisabled() {
runner.withPropertyValues("greeting.enabled=false")
.run(context -> assertThat(context).doesNotHaveBean(GreetingService.class));
}
}Common ApplicationContext Errors
Most startup failures come from the same few causes, and each exception message tells you where to look. NoSuchBeanDefinitionException means Spring found no bean of the required type: the class is missing a stereotype annotation, is outside the component-scan packages, or is excluded by a profile or condition. NoUniqueBeanDefinitionException means two or more beans match and Spring cannot choose: mark one @Primary or select one with @Qualifier. A circular dependency (BeanCurrentlyInCreationException) means two beans need each other during construction: break the cycle by redesigning, or as a last resort inject one side with @Lazy. A failure while the context is closing usually points at a @PreDestroy method that throws. Spring Boot's failure analyzers print a readable description and suggested action for many of these, so read the start of the log first.
Java
// ── NoSuchBeanDefinitionException ─────────────────────────────────────
// "No qualifying bean of type 'UserService' available"
// Checks:
// • Is the class annotated (@Service / @Component / @Repository)?
// • Is it in a package at or below the @SpringBootApplication class?
// (otherwise add @ComponentScan or @SpringBootApplication(scanBasePackages = ...))
// • Is it behind a @Profile or @ConditionalOnProperty that is not active?
// ── NoUniqueBeanDefinitionException ───────────────────────────────────
// "expected single matching bean but found 2: stripePayment, paypalPayment"
@Service @Primary
public class StripePaymentService implements PaymentService { }
// or select explicitly where it is injected:
public CheckoutService(@Qualifier("paypalPaymentService") PaymentService payments) { }
// or inject all of them:
public CheckoutService(List<PaymentService> allPayments) { }
// ── Circular dependency ───────────────────────────────────────────────
// A needs B and B needs A (constructor injection fails at startup):
@Service
class OrderService {
OrderService(InvoiceService invoices) { }
}
@Service
class InvoiceService {
InvoiceService(OrderService orders) { } // cycle
}
// Preferred fix: extract the shared logic into a third bean both can use,
// or publish an event instead of calling directly.
// Last resort: inject one side lazily:
// InvoiceService(@Lazy OrderService orders) { }
// Note: Spring Boot disallows circular references by default
// (spring.main.allow-circular-references=false).
// ── Inspect what is actually in the context ───────────────────────────
// Add to application.properties for a startup report of auto-configuration:
// debug=true
// Or with Actuator (secure it in production):
// management.endpoints.web.exposure.include=beans,conditions,env
// GET /actuator/beans — every bean and its dependencies
// GET /actuator/conditions — why each auto-configuration did or did not apply