Spring Boot
Spring Boot Internal Working
When you run a Spring Boot application, a sophisticated sequence of events unfolds in milliseconds — auto-configuration decisions are made, beans are created, an embedded server starts, and your application becomes ready to handle requests. Understanding this sequence demystifies the framework and makes debugging and customization far more effective.
The Entry Point — SpringApplication.run()
Everything starts with one line: SpringApplication.run(). This call bootstraps the entire Spring Boot application. Under the hood, it creates a SpringApplication instance, determines the application type, loads configuration, and starts the context.
Java
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
// This single line:
// 1. Creates a SpringApplication instance
// 2. Determines application type (SERVLET / REACTIVE / NONE)
// 3. Loads SpringFactories / AutoConfiguration.imports
// 4. Creates and refreshes ApplicationContext
// 5. Starts embedded web server
// 6. Runs CommandLineRunners and ApplicationRunners
}
}
// You can customize SpringApplication before run():
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
app.setBannerMode(Banner.Mode.OFF); // disable startup banner
app.setLazyInitialization(true); // lazy bean initialization
app.setDefaultProperties(Map.of( // programmatic properties
"server.port", "9090"
));
app.run(args);
}
}Step 1 — Application Type Detection
Spring Boot first determines what type of application it's running — a traditional Servlet-based web app, a reactive web app, or a non-web application. This determines which ApplicationContext class is used.
Java
// Spring Boot checks the classpath for these classes:
// If DispatcherServlet is present:
// → WebApplicationType.SERVLET
// → Creates AnnotationConfigServletWebServerApplicationContext
// → Starts embedded Tomcat/Jetty
// If DispatcherHandler is present (but not DispatcherServlet):
// → WebApplicationType.REACTIVE
// → Creates AnnotationConfigReactiveWebServerApplicationContext
// → Starts embedded Netty
// If neither is present:
// → WebApplicationType.NONE
// → Creates AnnotationConfigApplicationContext
// → No web server started (batch jobs, CLI tools, etc.)
// You can force the type:
SpringApplication app = new SpringApplication(MyApplication.class);
app.setWebApplicationType(WebApplicationType.NONE); // force non-web
app.run(args);Step 2 — @SpringBootApplication Unpacked
The @SpringBootApplication annotation triggers three distinct mechanisms that together bootstrap the entire application.
Java
// @SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan
// @ComponentScan scans the package and all sub-packages:
// Finds: @Component, @Service, @Repository, @Controller, @RestController,
// @Configuration, @Aspect, and other stereotype annotations
// Registers found classes as bean definitions in the BeanFactory
// @Configuration marks the class as a bean definition source:
// Any @Bean methods in the class produce beans
// @EnableAutoConfiguration is the key Spring Boot addition:
// It imports AutoConfigurationImportSelector
// Which reads META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
// (or META-INF/spring.factories in Boot < 2.7)
// This file lists ~150 auto-configuration classes
// Example entries in AutoConfiguration.imports:
// org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
// org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
// org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
// org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
// ... 140+ moreStep 3 — Auto-Configuration Decision Making
Each auto-configuration class is loaded conditionally. Spring Boot uses @Conditional annotations to decide whether each auto-configuration should apply based on what's available in the environment.
Java
// Example: DataSourceAutoConfiguration
// Spring Boot only configures a DataSource if:
// 1. DataSource class is on the classpath
// 2. No DataSource bean already defined by the developer
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration { }
@Configuration
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@ConditionalOnSingleCandidate(DataSource.class)
protected static class PooledDataSourceConfiguration { }
}
// The key @Conditional annotations:
// @ConditionalOnClass — applies if a class IS on the classpath
// @ConditionalOnMissingClass — applies if a class is NOT on the classpath
// @ConditionalOnBean — applies if a bean IS already defined
// @ConditionalOnMissingBean — applies if a bean is NOT already defined
// @ConditionalOnProperty — applies if a property has a specific value
// @ConditionalOnWebApplication — applies in web applications only
// @ConditionalOnExpression — applies based on a SpEL expression
// This is why defining your own bean OVERRIDES auto-configuration:
@Bean
public DataSource dataSource() {
// Your custom DataSource — @ConditionalOnMissingBean on auto-config
// sees your bean and skips auto-configuration entirely
return myCustomDataSource();
}Step 4 — ApplicationContext Refresh
After all bean definitions are registered (from component scanning and auto-configuration), the ApplicationContext is "refreshed" — beans are instantiated, dependencies injected, and lifecycle callbacks invoked.
Java
// The refresh() process in order:
// 1. BeanFactory preparation
// — Post-process the BeanFactory
// — Register BeanPostProcessors (e.g., AutowiredAnnotationBeanPostProcessor)
// 2. Bean instantiation
// — For each non-lazy singleton bean:
// a. Resolve constructor arguments
// b. Instantiate the bean (call constructor)
// c. Inject dependencies (@Autowired fields and setters)
// d. Call @PostConstruct methods
// e. Register with BeanFactory
// 3. ApplicationEvent publishing
// — ApplicationStartedEvent
// — ApplicationReadyEvent (after server starts)
// You can hook into this process:
// Run code after specific beans are created:
@Component
public class StartupValidator {
@PostConstruct
public void validate() {
// Called after this bean's dependencies are injected
System.out.println("Validating configuration...");
}
}
// Run code after the ENTIRE context is ready:
@Component
public class DataSeeder implements CommandLineRunner {
@Override
public void run(String... args) {
// Called after ApplicationContext is fully started
System.out.println("Seeding initial data...");
}
}
// ApplicationRunner — same but receives ApplicationArguments:
@Component
public class AppStartup implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
System.out.println("App started with args: " + args.getOptionNames());
}
}Step 5 — Embedded Server Startup
For web applications, Spring Boot starts the embedded server after the ApplicationContext is refreshed. The server is itself a Spring bean, configured by auto-configuration and properties.
Java
// TomcatServletWebServerFactory creates the embedded Tomcat:
// (auto-configured by ServletWebServerFactoryAutoConfiguration)
// What happens during server startup:
// 1. Tomcat is instantiated and configured
// — port from server.port property (default 8080)
// — connector configured (HTTP, HTTPS, compression settings)
// 2. Spring's DispatcherServlet registered with Tomcat
// — handles all incoming HTTP requests
// 3. Tomcat starts and binds to the configured port
// 4. ApplicationStartedEvent is published
// 5. CommandLineRunners and ApplicationRunners execute
// 6. ApplicationReadyEvent is published — app is ready
// Customize the embedded server:
@Configuration
public class ServerConfig {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
return factory -> {
factory.setPort(9090);
factory.setConnectionTimeout(Duration.ofSeconds(10));
factory.addConnectorCustomizers(connector -> {
connector.setMaxPostSize(10 * 1024 * 1024); // 10MB max request
});
};
}
}
// Or via application.properties:
// server.port=8080
// server.tomcat.max-connections=10000
// server.tomcat.threads.max=200
// server.tomcat.threads.min-spare=10
// server.compression.enabled=true
// server.ssl.key-store=classpath:keystore.p12Spring Boot Application Events
Spring Boot publishes events at each stage of startup. You can listen to these events to execute code at precise points in the lifecycle.
Java
// Application events in order of publication:
// 1. ApplicationStartingEvent — very early, before anything is prepared
// 2. ApplicationEnvironmentPreparedEvent — environment ready, context not created yet
// 3. ApplicationContextInitializedEvent — context created, beans not loaded
// 4. ApplicationPreparedEvent — context loaded, not refreshed
// 5. ApplicationStartedEvent — context refreshed, before runners
// 6. AvailabilityChangeEvent (LivenessState.CORRECT) — app is live
// 7. ApplicationReadyEvent — runners have run, app fully started
// 8. AvailabilityChangeEvent (ReadinessState.ACCEPTING_TRAFFIC) — app ready for traffic
// 9. ApplicationFailedEvent — if startup fails at any point
// Listening to events:
@Component
public class StartupListener {
@EventListener(ApplicationReadyEvent.class)
public void onReady(ApplicationReadyEvent event) {
System.out.println("Application fully started and ready!");
// Safe to make external connections, start schedulers, etc.
}
@EventListener(ApplicationFailedEvent.class)
public void onFailure(ApplicationFailedEvent event) {
System.err.println("Startup failed: " + event.getException().getMessage());
}
}
// Early events (before context creation) need special registration:
public class EarlyListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
System.out.println("Environment ready: " + event.getEnvironment().getActiveProfiles());
}
}
// Register in META-INF/spring.factories:
// org.springframework.context.ApplicationListener=com.example.EarlyListenerDebugging Auto-Configuration
When auto-configuration behaves unexpectedly, Spring Boot provides tools to understand exactly what was configured and why.
Java
// Enable auto-configuration report:
// application.properties:
debug=true
// Or: java -jar app.jar --debug
// The report shows three sections:
// POSITIVE MATCHES — auto-configs that were applied and why:
// DataSourceAutoConfiguration matched:
// - @ConditionalOnClass: found DataSource (spring.datasource.url is set)
// - @ConditionalOnMissingBean: no DataSource bean found
// NEGATIVE MATCHES — auto-configs that were skipped and why:
// MongoAutoConfiguration did not match:
// - @ConditionalOnClass: MongoClient not found (no MongoDB dependency)
// EXCLUSIONS — auto-configs you explicitly disabled:
// SecurityAutoConfiguration — excluded by user
// Exclude specific auto-configurations:
@SpringBootApplication(exclude = {
SecurityAutoConfiguration.class,
DataSourceAutoConfiguration.class
})
public class Application { }
// Or in application.properties:
// spring.autoconfigure.exclude=// org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
// See all beans in the context:
// GET /actuator/beans (with Actuator enabled)
// Or programmatically:
@Component
public class BeanPrinter implements CommandLineRunner {
@Autowired
ApplicationContext context;
@Override
public void run(String... args) {
Arrays.stream(context.getBeanDefinitionNames()).sorted().forEach(System.out::println);
}
}