The initialization of a Spring Boot application follows a highly structured pipeline orchestrated by the SpringApplication bootstrap class. Understanding this lifecycle is crucial for debugging, performance tuning, and framework extension. The entire process can be segmented into five distinct operational phases:
- Phase 1: Initialization & Deployment Model Detection
- Phase 2: Enviroment & Configuration Binding
- Phase 3: Application Context Instantiation
- Phase 4: Bean Factory Refresh & Lifecycle Processing
- Phase 5: Post-Startup Execution & Runner Invocation
- The Bootstrap Entry Point
Every Spring Boot application begins execution through a static runner method. While the standard template uses a single line, the underlying delegation involves constructing a bootstrapper instance and invoking its lifecycle controller.
@SpringBootApplication
public class ServiceGatewayApplication {
public static void main(String[] cliArgs) {
BootstrapExecutor.launch(ServiceGatewayApplication.class, cliArgs);
}
}
- Internal Construction of the Bootstrap Manager
When the runtime environment instantiates the core bootstrapper, it performs several preparatory checks. It infers the deployment model (Servlet, WebFlux, or non-web), registers lifecycle callbacks, and locates the primary entry class.
public class ApplicationBootstrapper {
private final Set<Class<?>> entryPoints;
private WebDeploymentModel targetModel;
public static ApplicationContext launch(Class<?> mainClass, String[] args) {
return new ApplicationBootstrapper(mainClass).execute(args);
}
public ApplicationBootstrapper(Class<?> mainClass) {
this.entryPoints = Set.of(mainClass);
this.targetModel = detectDeploymentModel();
registerLifecycleCallbacks();
this.primaryClass = resolveMainClass(mainClass);
}
private WebDeploymentModel detectDeploymentModel() {
// Inspects classpath for Servlet or Reactive markers
return ClasspathScanner.inferModel();
}
private void registerLifecycleCallbacks() {
// Loads SPI definitions for initializers and event listeners
List<?> initHandlers = SpiLoader.scan(ApplicationContextInitializer.class);
List<?> eventListeners = SpiLoader.scan(ApplicationListener.class);
// Stores them internally for later invocation
}
}
The detection mechanism scanss the classpath for specific marker interfaces to determine whether to configure a traditional servlet container, a reactive web server, or a standard standalone context. SPI loading replaces hardcoded dependencies, allowing third-party libraries to inject custom initializers and event subscribers.
- The Core Execution Pipeline
The execute method orchestrates the entire startup sequence. It wraps the process in a timing utility, fires start events, and safely handles failures.
public ApplicationContext execute(String[] cliArgs) {
var timer = new ExecutionTimer().start();
ApplicationContext container = null;
var eventBroadcasters = assembleEventPublishers(cliArgs);
eventBroadcasters.triggerStart();
try {
var parsedArgs = new CommandLineArguments(cliArgs);
var configEnv = configureRuntimeEnvironment(eventBroadcasters, parsedArgs);
displayBootBanner(configEnv);
// Instantiate the appropriate container type
container = instantiateApplicationContainer();
container.setConfigurableEnvironment(configEnv);
// Populate container with initializers and property sources
primeContainer(container, configEnv, eventBroadcasters, parsedArgs);
// Trigger full lifecycle processing
refreshBeanFactory(container);
// Execute post-refresh hooks
finalizeStartup(container, parsedArgs);
timer.stop();
eventBroadcasters.triggerStarted(container);
invokeStartupRunners(container, parsedArgs);
} catch (RuntimeException | Error fatalError) {
logFatalException(fatalError);
failStartup(container, fatalError, eventBroadcasters);
throw new StartupAbortedException(fatalError);
}
eventBroadcasters.triggerRunning(container);
return container;
}
- Phase Breakdown: Environment & Context Resolution
Configuration Environment Assembly
Before any beans are instantiated, the framework must resolve the active configuration profile, bind external properties, and parse command-line overrides.
private ConfigurableEnvironment configureRuntimeEnvironment(
EventPublisher broadcaster, ParsedArguments args) {
ConfigurableEnvironment env = buildOrRetrieveEnvironment();
applyCommandLineProperties(env, args.getRawArgs());
bindPropertySources(env);
// Notifies all registered listeners that properties are ready
broadcaster.publishEnvironmentReady(env);
// Binds standard Spring Boot properties to the bootstrap object
bindStandardProperties(env);
return env;
}
This phase merges YAML/Properties files, environment variables, and CLI flags (prefixed with --). Once resolved, an ApplicationEnvironmentPreparedEvent is broadcasted, allowing listeners to modify the environment before context creation.
Context Instantiation Strategy
The container type is dynamically selected based on the previously detected deployment model. A factory pattern ensures the correct implementation is wired without coupling the bootstrap logic to specific context classes.
private ApplicationContext instantiateApplicationContainer() {
return ContainerFactory.produce(this.targetModel);
}
// Internal factory mapping
enum ContainerFactory {
;
static ApplicationContext produce(WebDeploymentModel model) {
return switch (model) {
case SERVLET -> new StandardServletWebContext();
case REACTIVE -> new ReactiveWebServerContext();
case NONE -> new StandaloneApplicationContext();
};
}
}
Bean Factory Refresh Cycle
The most computationally intensive phase occurs during the refresh cycle. It delegates to the underlying Spring Framework's AbstractApplicationContext.refresh() method, which handles bean definition parsing, dependency injection, AOP proxy creation, and singleton pre-instantiation.
private void refreshBeanFactory(ApplicationContext container) {
performRefresh(container);
if (enableGracefulShutdown) {
try {
Runtime.getRuntime().addShutdownHook(container.createShutdownThread());
} catch (SecurityException restrictedEnv) {
// Ignore in restricted runtime environments
}
}
}
protected void performRefresh(ApplicationContext target) {
((RefreshableApplicationContext) target).refresh();
}
The refresh() method executes a synchronized, multi-step routine: preparing the refresh state, loading bean definitions, invoking bean factory post-processors, registering bean post-processors, initializing message sources, preparing the web environment, instantiating non-lazy singletons, and finally publishing the ContextRefreshedEvent.
- Lifecycle Hooks & Extension Mechanisms
Spring Boot exposes several integration points that allow developers to intervene at specific stages of the bootstrap sequence.
Pre-Context Initializers
Implementations of ApplicationContextInitializer are invoked before the context refresh begins. They receive a mutable reference to the application context, enabling programmatic bean definition registration or property modification.
public interface PreContextInitializer<C extends ConfigurableApplicationContext> {
void apply(C ctx);
}
These components are typically discovered via SPI mechanisms or explicitly registered via the bootstrap builder API. They operate during the primeContainer phase, ensuring custom logic runs before dependency resolution begins.
Post-Startup Runners
Once the bean factory is fully populated and the context is refreshed, the framework locates and executes components implementing ApplicationRunner or CommandLineRunner. These are ideal for data seeding, health checks, or starting background workers.
@Component
@Order(10)
public class DatabaseSeeder implements ApplicationRunner {
@Override
public void run(ApplicationArguments runtimeArgs) {
// Logic executed after full context initialization
if (runtimeArgs.containsOption("seed-db")) {
populateTestData();
}
}
}
Execution order among multiple runners is strictly governed by the @Order annotation or the Ordered interface. The framework sorts these components before invocation, ensuring deterministic startup behavior.
- Visualizing the Execution Path
The following diagram outlines the sequential flow from JVM entry to application readiness:
main()
└──▶ ApplicationBootstrapper.launch()
├──▶ Detect deployment model & load SPI callbacks
├──▶ Resolve properties, profiles & CLI arguments
├──▶ Instantiate target ApplicationContext
├──▶ Wire environment & apply initializers
├──▶ Execute refresh() (Bean parsing & DI)
├──▶ Invoke ApplicationRunner / CommandLineRunner
└──▶ Broadcast Running event & return context