Spring Boot Application Startup Lifecycle
SpringApplication Initialization
When launching a Spring Boot application, the typical entry point is:
SpringApplication.run(MainApplication.class, args);
The run method is a static method that delegatees to another overloaded version:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
This method performs two key operations:
- Instantiating a
SpringApplicationobject - Invoking the
runmethod on that instance
Constructor Breakdown
The SpringApplication constructor performs the following initialization steps:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
Step-by-step explanation:
-
this.resourceLoader = resourceLoader: Sets the resource loader for loading configurations and class files. When null, Spring Boot's auto-configuration handles resource loading. -
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)): Stores the source classes containing@SpringBootApplicationor@Configurationannotations. These classes define the bean definitions for the Spring context. -
this.webApplicationType = WebApplicationType.deduceFromClasspath(): Determines the web application type by inspecting classpath dependencies—SERVLET, REACTIVE, or NONE. -
this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class)): Loads allBootstrapRegistryInitializerimplementations fromMETA-INF/spring.factories. These initializers prepare the bootstrap registry for sharing objects before the Spring container exists. -
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)): LoadsApplicationContextInitializerimplementations to extend the context after creation but before refresh. -
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)): LoadsApplicationListenerimplementations for event handling during the application lifecycle. -
this.mainApplicationClass = deduceMainApplicationClass(): Identifies the class containing themainmethod.
Note that the Spring container is not created during constructoin—creation happens during the run phase.
Practical Demonstration
Bean Definition Sources
The primarySources parameter represents the initial source of bean definitions, typically the main application class:
public class DemoApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(DemoApplication.class);
ConfigurableApplicationContext ctx = app.run(args);
for (String beanName : ctx.getBeanDefinitionNames()) {
System.out.println("Bean: " + beanName + " - Source: " +
ctx.getBeanFactory().getBeanDefinition(beanName).getResourceDescription());
}
ctx.close();
}
static class ServiceA {}
static class ServiceB {}
static class ServiceC {}
@Bean
public ServiceB serviceB() {
return new ServiceB();
}
@Bean
public TomcatServletWebServerFactory webServerFactory() {
return new TomcatServletWebServerFactory();
}
}
Running this produces output showing bean definitions with their sources. Internal Spring beans show null as their source, while user-defined beans show their originating class.
Additional sources can be added programmatically:
app.setSources(new HashSet<String>() {{ add("classpath:beans.xml"); }});
Web Application Type Detection
The WebApplicationType.deduceFromClasspath() method determines the application type:
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) &&
!ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) &&
!ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
Detection logic:
- If reactive classes exist without servlet classes → REACTIVE
- If neither javax.servlet.Servlet nor ConfigurableWebApplicationContext exist → NONE
- Otherwise → SERVLET
ApplicationContext Initializers
Initializers allow extending the context before refresh:
app.addInitializers(applicationContext -> {
if (applicationContext instanceof GenericApplicationContext) {
GenericApplicationContext gac = (GenericApplicationContext) applicationContext;
gac.registerBean("customService", CustomService.class);
}
});
Event Listeners
The ApplicationListener interface handles application lifecycle events:
app.addListeners(event -> System.out.println("Event: " + event.getClass()));
Key events include:
ApplicationStartingEventApplicationEnvironmentPreparedEventApplicationContextInitializedEventApplicationPreparedEventContextRefreshedEventApplicationStartedEventApplicationReadyEventApplicationFailedEvent
Main Application Class Detection
Method method = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
method.setAccessible(true);
Class<?> mainClass = (Class<?>) method.invoke(app);
The run Method
Core Workflow
- Create
SpringApplicationRunListenersfor event publishing - Publish
ApplicationStartingEvent - Prepare environment with command-line arguments
- Publish
ApplicationEnvironmentPreparedEvent - Execute
EnvironmentPostProcessorplugins - Bind
spring.main.*properties to SpringApplication - Print banner
- Create ApplicationContext
- Prepare context (call initializers)
- Publish
ApplicationContextInitializedEvent - Load bean definitions
- Publish
ApplicationPreparedEvent - Refresh context
- Publish
ApplicationStartedEvent - Execute runners
- Publish
ApplicationReadyEvent
Event Publishing Implementation
Spring Boot uses EventPublishingRunListener to broadcast lifecycle events:
public class EventPublisherDemo {
public static void main(String[] args) throws Exception {
SpringApplication app = new SpringApplication();
app.addListeners(event -> System.out.println("Event: " + event.getClass()));
List<String> listenerClasses = SpringFactoriesLoader
.loadFactoryNames(SpringApplicationRunListener.class,
EventPublisherDemo.class.getClassLoader());
for (String className : listenerClasses) {
Class<?> clazz = Class.forName(className);
Constructor<?> ctor = clazz.getConstructor(SpringApplication.class, String[].class);
SpringApplicationRunListener publisher =
(SpringApplicationRunListener) ctor.newInstance(app, args);
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
publisher.starting(bootstrapContext);
publisher.environmentPrepared(bootstrapContext, new StandardEnvironment());
GenericApplicationContext context = new GenericApplicationContext();
publisher.contextPrepared(context);
publisher.contextLoaded(context);
context.refresh();
publisher.started(context);
publisher.running(context);
publisher.failed(context, new RuntimeException("Failed"));
}
}
}
ApplicationContext Creation
The context type depends on webApplicationType:
private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
return switch (type) {
case SERVLET -> new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE -> new AnnotationConfigReactiveWebServerApplicationContext();
case NONE -> new AnnotationConfigApplicationContext();
};
}
Bean Definition Loading
Multiple strategies load bean definitions:
DefaultListableBeanFactory factory = context.getDefaultListableBeanFactory();
// Annotation-based
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(factory);
reader.register(Config.class);
// XML-based
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(factory);
xmlReader.loadBeanDefinitions(new ClassPathResource("beans.xml"));
// Classpath scanning
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(factory);
scanner.scan("com.example.beans");
context.refresh();
CommandLineRunner and ApplicationRunner
These interfaces execute code after the context refreshes:
@Bean
public CommandLineRunner commandLineRunner() {
return args -> System.out.println("Args: " + Arrays.toString(args));
}
@Bean
public ApplicationRunner applicationRunner() {
return args -> {
System.out.println("Source Args: " + Arrays.toString(args.getSourceArgs()));
System.out.println("Options: " + args.getOptionNames());
System.out.println("Non-options: " + args.getNonOptionArgs());
};
}
Environment Configuration
ApplicationEnvironment manages property sources with a priority order:
ApplicationEnvironment env = new ApplicationEnvironment();
// Command-line properties have highest priority
env.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));
// Custom properties added to end
env.getPropertySources().addLast(
new ResourcePropertySource("custom", new ClassPathResource("config.properties")));
for (PropertySource<?> ps : env.getPropertySources()) {
System.out.println(ps.getName());
}
EnvironmentPostProcessor
This interface modifies the environment before the application starts:
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
// Add custom property sources based on environment variables
}
}
Configuration in META-INF/spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=com.example.CustomEnvironmentPostProcessor
Property Binding
Use Binder to bind configuration properties to objects:
SpringApplication app = new SpringApplication();
ApplicationEnvironment env = new ApplicationEnvironment();
env.getPropertySources().addLast(
new ResourcePropertySource("config", new ClassPathResource("app.properties")));
// Bind properties with prefix
Binder.get(env).bind("spring.main", Bindable.ofInstance(app));
The Binder clas handles conversion from property sources to typed objects, supporting nested properties and collection types.