Spring BootSpring Boot Internal Working
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+ more

Step 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.p12

Spring 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.EarlyListener

Debugging 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);
    }
}