1.1. The main() Entry Point — Anatomy of the Startup
Every Spring Boot application starts the same way:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
That single line sets the entire machinery in motion.
1.2. Static run() vs. a SpringApplication Instance
There are two ways to launch an application:
Approach 1 — the static method (95% of cases):
SpringApplication.run(MyApplication.class, args);
Under the hood, it does this:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return new SpringApplication(primarySource).run(args);
}
So a SpringApplication instance is created first, and then its instance method run() is called. This is important to understand: all configuration happens in the constructor, while run() is pure execution.
Approach 2 — manual instantiation (when you need customization before the run):
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
app.setBannerMode(Banner.Mode.OFF);
app.setAdditionalProfiles("dev");
app.setWebApplicationType(WebApplicationType.SERVLET);
app.run(args);
}
This is exactly why a separate class exists — SpringApplicationBuilder — for fluent configuration:
new SpringApplicationBuilder(MyApplication.class)
.profiles("dev")
.bannerMode(Banner.Mode.OFF)
.web(WebApplicationType.SERVLET)
.run(args);
SpringApplicationBuilder is especially useful when you need to build a parent-child context hierarchy:
new SpringApplicationBuilder(ParentConfig.class)
.child(ChildConfig.class)
.run(args);
This approach is used in Spring Cloud (the bootstrap context) and in testing scenarios.
1.3. The SpringApplication Constructor — What Happens Before run()
The constructor does three key things:
1. Deducing the application type (WebApplicationType)
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// ... checks for SERVLET and NONE
}
Spring Boot scans the classpath:
-
DispatcherServletpresent (Spring MVC) →SERVLET -
DispatcherHandlerpresent (WebFlux) without MVC →REACTIVE - No web classes →
NONE
2. Loading the ApplicationContextInitializers and ApplicationListeners
Via SpringFactoriesLoader, it reads META-INF/spring.factories:
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
...
| Aspect | ApplicationContextInitializer | ApplicationListener |
|---|---|---|
| Pattern | Initialization strategy (Template Method) | Observer |
| Main purpose | Configure / modify the context and environment | React to what's happening with the application |
| Timing | Exactly once: before refresh() (before beans are created) | Multiple times: at startup, at runtime, on shutdown |
| Access to beans | No (beans don't exist yet) | Yes (if you listen to later events or work inside a bean) |
| Typical example | Pull secrets from Vault into the Environment | Send a Telegram alert on ApplicationReadyEvent |
3. Setting the primary sources
MyApplication.class is stored as primarySources — the root @Configuration class from which component scanning will start.
1.4. The Instance run() Method — a Step-by-Step Breakdown
Here is the skeleton of the run(String... args) method (Spring Boot 3.x):
public ConfigurableApplicationContext run(String... args) {
// 1. Create the BootstrapContext
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 2. Headless mode (java.awt.headless)
configureHeadlessProperty();
// 3. Get the run listeners
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 4. Prepare the Environment
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
bootstrapContext, applicationArguments);
// 5. Print the banner
Banner printedBanner = printBanner(environment);
// 6. Create the ApplicationContext
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 7. Prepare the context
prepareContext(bootstrapContext, context, environment, listeners,
applicationArguments, printedBanner);
// 8. Refresh — the key stage
refreshContext(context);
// 9. afterRefresh (hook)
afterRefresh(context, applicationArguments);
// 10. Publish ApplicationStartedEvent
listeners.started(context, timeTakenToStartup);
// 11. Call ApplicationRunner / CommandLineRunner
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
// 12. Publish ApplicationReadyEvent
listeners.ready(context, timeTakenToReady);
return context;
}
Step 1: DefaultBootstrapContext
BootstrapContext is a container for objects that are available before the ApplicationContext is created. It lives from the start of run() until the context is ready. It is used for registering early beans, such as the EnvironmentPostProcessor.
Step 2: configureHeadlessProperty()
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
Boolean.toString(this.headless)));
}
Sets java.awt.headless=true by default. This matters for server-side applications — it disables AWT GUI components.
Step 3: SpringApplicationRunListeners — the heart of the event mechanism
This is the SPI interface for startup lifecycle listeners:
public interface SpringApplicationRunListener {
void starting(ConfigurableBootstrapContext bootstrapContext);
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void started(ConfigurableApplicationContext context, Duration timeTaken);
void ready(ConfigurableApplicationContext context, Duration timeTaken);
void failed(ConfigurableApplicationContext context, Throwable exception);
}
It is loaded via SpringFactoriesLoader from META-INF/spring.factories. Spring Boot ships a single implementation — EventPublishingRunListener.
EventPublishingRunListener is a bridge between SpringApplicationRunListener and ApplicationEvent. It translates every lifecycle callback into the corresponding event.
| Callback | Event |
|---|---|
starting() |
ApplicationStartingEvent |
environmentPrepared() |
ApplicationEnvironmentPreparedEvent |
contextPrepared() |
ApplicationContextInitializedEvent |
contextLoaded() |
ApplicationPreparedEvent |
started() |
ApplicationStartedEvent |
ready() |
ApplicationReadyEvent |
failed() |
ApplicationFailedEvent |
The complete sequence of events:
| Event | When | Context available? |
|---|---|---|
ApplicationStartingEvent |
Before the Environment and Context exist | No |
ApplicationEnvironmentPreparedEvent |
Environment is ready, Context is not yet | No |
ApplicationContextInitializedEvent |
Context created, beans not yet loaded | Yes (empty) |
ApplicationPreparedEvent |
Beans loaded, refresh() not yet called |
Yes (not refreshed) |
ApplicationStartedEvent |
refresh() done, runners not yet invoked |
Yes (refreshed) |
AvailabilityChangeEvent (Liveness) |
Right after ApplicationStartedEvent | Yes |
ApplicationReadyEvent |
All runners have been executed | Yes (refreshed) |
AvailabilityChangeEvent (Readiness) |
Right after ApplicationReadyEvent
|
Yes |
ApplicationFailedEvent |
On any unhandled exception | Partially |
Step 4: prepareEnvironment() — preparing the environment
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ConfigurableBootstrapContext bootstrapContext,
ApplicationArguments applicationArguments) {
// 1. Create or reuse the Environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. Configure it: command line args, profiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 3. Attach the ConfigurationPropertySources
ConfigurationPropertySources.attach(environment);
// 4. Publish ApplicationEnvironmentPreparedEvent
listeners.environmentPrepared(bootstrapContext, environment);
// 5. Bind spring.main.*
DefaultPropertiesPropertySource.moveToEnd(environment);
bindToSpringApplication(environment);
// 6. Convert the Environment if necessary
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
Key points:
-
getOrCreateEnvironment()— createsStandardServletEnvironment/StandardReactiveEnvironment/StandardEnvironment -
configureEnvironment()— adds aCommandLinePropertySourceand sets the active profiles -
listeners.environmentPrepared()— this is where theEnvironmentPostProcessorskick in (e.g., loadingapplication.yml)
Step 5: printBanner() — printing the banner
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null)
? this.resourceLoader : new DefaultResourceLoader(null);
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Banner.Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
- A
banner.txtfile insrc/main/resources/is picked up automatically -
spring.banner.location— specify a custom file path -
spring.main.banner-mode=off— disable the banner - Spring Boot 3.0.0 M2+: image banner support (PNG/JPEG/GIF) was removed; only banner.txt remains
Variables available in the banner: ${spring-boot.version}, ${application.version}, ${application.formatted-version}
Step 6: createApplicationContext() — choosing the context type
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
Spring Boot 3 uses an ApplicationContextFactory (an enum-like factory):
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch (webApplicationType) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
} catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
};
In Spring Boot 4, ApplicationContextFactory has been removed. Instead, a modular system is used — each module (spring-boot-webmvc, spring-boot-webflux) registers its own context via module dependencies. AbstractApplicationContextFactory is marked as @Deprecated(since="6.0", forRemoval=true).
Step 7: prepareContext() — preparing the context
private void prepareContext(DefaultBootstrapContext bootstrapContext,
ConfigurableApplicationContext context,
ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments,
Banner printedBanner) {
// 1. Set the Environment
context.setEnvironment(environment);
// 2. postProcessApplicationContext (bean name generator, resource loader)
postProcessApplicationContext(context);
// 3. Apply the ApplicationContextInitializers
applyInitializers(context);
// 4. Publish ApplicationContextInitializedEvent
listeners.contextPrepared(context);
// 5. Register singleton beans: springApplicationArguments, springBootBanner
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory dlbf) {
dlbf.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(
new LazyInitializationBeanFactoryPostProcessor());
}
// 6. Load all the sources
Set<Object> sources = getAllSources();
load(context, sources.toArray(new Object[0]));
// 7. Publish ApplicationPreparedEvent
listeners.contextLoaded(context);
}
The key moment is applyInitializers(): this is where all ApplicationContextInitializers loaded from spring.factories get invoked. It's the mechanism for customizing the context before any beans are loaded.
Step 8: refreshContext() — starting the Spring container
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException ex) {
// Not allowed in some environments
}
}
refresh(context);
}
The refresh() method here is AbstractApplicationContext.refresh() from Spring Framework. This is where:
- All singleton beans are created
-
BeanFactoryPostProcessorsrun →ConfigurationClassPostProcessorprocesses@Configuration -
AutoConfigurationImportSelectorruns → auto-configuration -
BeanPostProcessorsrun - The embedded web server starts
Step 9: afterRefresh() — a hook
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}
An empty hook meant to be overridden in subclasses.
Step 10: listeners.started() — ApplicationStartedEvent
ApplicationStartedEvent and AvailabilityChangeEvent(LivenessState.CORRECT) are published. From this moment on, the application is considered started, but it is not yet ready to serve traffic.
Step 11: callRunners() — ApplicationRunner and CommandLineRunner
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner applicationRunner) {
callRunner(applicationRunner, args);
}
if (runner instanceof CommandLineRunner commandLineRunner) {
callRunner(commandLineRunner, args);
}
}
}
-
ApplicationRunner— receivesApplicationArguments(parsed) -
CommandLineRunner— receives a rawString[] - They are sorted via
@Order/Ordered
Step 12: listeners.ready() — ApplicationReadyEvent
ApplicationReadyEvent and AvailabilityChangeEvent(ReadinessState.ACCEPTING_TRAFFIC) are published. From this point on, the application is ready to accept traffic.
1.5. Error Handling — handleRunFailure()
private void handleRunFailure(ConfigurableApplicationContext context,
Throwable exception, SpringApplicationRunListeners listeners) {
try {
try {
handleExitCode(context, exception);
if (listeners != null) {
listeners.failed(context, exception);
}
} finally {
reportFailure(getExceptionReporters(context), exception);
if (context != null) {
context.close();
}
}
} catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
ReflectionUtils.rethrowRuntimeException(exception);
}
-
handleExitCode()— determines the exit code viaExitCodeExceptionMapper -
listeners.failed()— publishesApplicationFailedEvent -
FailureAnalyzer— attempts to produce a human-readable description of the failure - The context is closed
1.6. Startup Sequence Diagram
main()
│
├─ new SpringApplication(primarySource)
│ ├─ deduceWebApplicationType() → SERVLET / REACTIVE / NONE
│ ├─ load SpringFactories (Initializers, Listeners)
│ └─ set primarySources
│
└─ .run(args)
│
├─ createBootstrapContext()
├─ configureHeadlessProperty()
├─ getRunListeners() → EventPublishingRunListener
├─ listeners.starting() → ApplicationStartingEvent
│
├─ prepareEnvironment()
│ ├─ getOrCreateEnvironment() → StandardServletEnvironment
│ ├─ configureEnvironment() → CommandLinePropertySource, profiles
│ ├─ ConfigurationPropertySources.attach()
│ └─ listeners.environmentPrepared()→ ApplicationEnvironmentPreparedEvent
│ └─ EnvironmentPostProcessor → ConfigDataEnvironmentPostProcessor
│ └─ loading application.yml/properties
│
├─ printBanner() → banner.txt → System.out
│
├─ createApplicationContext()
│ └─ ApplicationContextFactory → AnnotationConfigServletWebServerApplicationContext
│
├─ prepareContext()
│ ├─ setEnvironment()
│ ├─ postProcessApplicationContext()
│ ├─ applyInitializers() → ApplicationContextInitializer
│ ├─ listeners.contextPrepared() → ApplicationContextInitializedEvent
│ ├─ registerSingleton(springApplicationArguments)
│ ├─ registerSingleton(springBootBanner)
│ ├─ load(sources) → BeanDefinitionLoader
│ └─ listeners.contextLoaded() → ApplicationPreparedEvent
│
├─ refreshContext()
│ ├─ registerShutdownHook()
│ └─ AbstractApplicationContext.refresh()
│ ├─ invokeBeanFactoryPostProcessors()
│ │ └─ ConfigurationClassPostProcessor → @Configuration, @Bean
│ │ └─ AutoConfigurationImportSelector → auto-configuration
│ ├─ registerBeanPostProcessors()
│ ├─ initMessageSource()
│ ├─ initApplicationEventMulticaster()
│ ├─ onRefresh()
│ │ └─ ServletWebServerApplicationContext.createWebServer()
│ │ └─ TomcatServletWebServerFactory → Tomcat.start()
│ └─ finishRefresh()
│
├─ afterRefresh() → hook
├─ listeners.started() → ApplicationStartedEvent
│ AvailabilityChangeEvent(Liveness)
│
├─ callRunners()
│ ├─ ApplicationRunner
│ └─ CommandLineRunner
│
└─ listeners.ready() → ApplicationReadyEvent
AvailabilityChangeEvent(Readiness)
1.7. Spring Boot 3 vs Spring Boot 4 — Key Bootstrap Differences
| Aspect | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
ApplicationContextFactory |
Enum-like DEFAULT factory, picks a context based on WebApplicationType
|
Removed. The context is determined by module dependencies (spring-boot-webmvc / spring-boot-webflux) |
spring-boot-autoconfigure |
A single monolithic JAR (6.2 MB) | 47 modular JARs, one per technology |
| Starters | spring-boot-starter-web |
spring-boot-starter-webmvc (renamed) |
spring.factories |
SPI for auto-configuration |
META-INF/spring/...AutoConfiguration.imports (already in 3.x, mandatory in 4.x) |
| Undertow | Supported | Removed (incompatible with Servlet 6.1) |
| Java baseline | Java 17+ | Java 21+ |
| Spring Framework | 6.x | 7.0 |
| Jackson | Jackson 2.x | Jackson 3.x (namespace changes) |
| Banner (image) | PNG/JPEG/GIF removed since 3.0 M2 |
banner.txt only |
Key Takeaways
-
SpringApplication.run()is a facade. All the real work happens in the instancerun()method. - The
SpringApplicationconstructor deduces theWebApplicationTypefrom the classpath and loads the SPIs. -
SpringApplicationRunListeneris an extension point for hooking into the lifecycle.EventPublishingRunListenertranslates those calls intoApplicationEvents. - The
Environmentis prepared before theApplicationContext— that's whyEnvironmentPostProcessors can add property sources before any beans exist. -
refreshContext()is the most important step. This is where all the beans are created,@Configurationclasses are processed, and Tomcat/Netty starts. -
ApplicationStartedEvent≠ApplicationReadyEvent. The former means the context is up; the latter means the runners have executed and the application is ready to accept traffic. - Spring Boot 4 is modular.
ApplicationContextFactoryis gone — the context is determined by your dependencies.
Top comments (0)