Spring Boot Startup Process

Spring Boot startup involves: loading config, creating the ApplicationContext, scanning and registering beans, and running Runners. This article explains the flow, key extension points, and how to diagnose slow startup with a reference table.

Overview

  • Entry point: SpringApplication.run(). Processes @SpringBootApplication (which includes @Configuration, @ComponentScan, @EnableAutoConfiguration).
  • Context: Creates the appropriate ApplicationContext (e.g. ServletWebServerApplicationContext for web apps). Loads bean definitions, instantiates beans, injects dependencies, and runs initialization callbacks.
  • Auto-configuration: @EnableAutoConfiguration loads auto-configuration classes based on the classpath and @ConditionalOnXxx conditions (e.g. DataSourceAutoConfiguration, WebMvcAutoConfiguration).
  • Runners: ApplicationRunner and CommandLineRunner beans run after the context is fully initialized; used for startup tasks, warmup, etc.

Example

Example 1: Startup flow overview

Plain text
run() → create ApplicationContext
      → load bean definitions (component scan, @Import, auto-config)
      → refresh(): instantiate, inject, run init callbacks
      → start embedded web server (if applicable)
      → run ApplicationRunner and CommandLineRunner beans
  • The refresh() phase is where most of the work happens: bean creation, @PostConstruct, BeanPostProcessor, AOP proxy creation, etc.

Example 2: Extension points

ExtensionWhen it runs
ApplicationContextInitializerBefore context refresh
BeanPostProcessorBefore/after each bean is created
ApplicationRunner / CommandLineRunnerAfter context is ready
@PostConstructAfter bean properties are injected

Example 3: Auto-configuration in action

  • With tomcat and spring-webmvc on the classpath: auto-configures the embedded Tomcat and DispatcherServlet.
  • With spring-boot-starter-data-jpa and a DataSource bean or config: auto-configures JPA and the data source.
  • You can exclude auto-configurations with @SpringBootApplication(exclude = {...}) or override with your own @Bean definitions.

Example 4: ApplicationRunner

Java
@Component
public class MyRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // Runs after the context is fully ready
        // args.getNonOptionArgs(), args.getOptionNames(), etc.
    }
}
  • Use for startup tasks that need the full context (e.g. preloading caches, registering with a service registry).
  • Avoid long-running or blocking work; use @Async or a separate thread pool if needed.

Example 5: CommandLineRunner

Java
@Component
public class CliRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        // Same idea as ApplicationRunner but with raw String[] args
    }
}
  • Simpler than ApplicationRunner when you only need raw args. Both run after the context is ready.

Example 6: BeanPostProcessor for custom init

Java
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        // Called after @PostConstruct, InitializingBean, init-method
        return bean;
    }
}
  • Use when you need to process every bean (e.g. for logging, validation, or custom metadata).

Core Mechanism / Behavior

  • @SpringBootApplication: Combines @Configuration, @ComponentScan (on the package of the annotated class and below), and @EnableAutoConfiguration.
  • Conditional loading: @ConditionalOnClass, @ConditionalOnProperty, @ConditionalOnBean, etc. control when auto-configurations apply. This keeps the context lean when dependencies are absent.
  • Refresh phase: AbstractApplicationContext.refresh() loads definitions, creates singletons, runs post-processors, and starts the web server. This is where startup time is spent.

Diagnosing Slow Startup

  • Too many beans: Reduce component scan scope; avoid unnecessary @Component on large packages.
  • Heavy @PostConstruct: Move blocking or slow logic to Runners, async init, or lazy beans.
  • Database connection pool: Pool init can be slow; consider lazy init or smaller initial size.
  • Auto-configuration: Use debug=true or actuator to see which auto-configurations are applied; exclude unused ones.

Key Rules

  • Slow startup: Check bean count, auto-config, blocking logic in @PostConstruct, DB pool init, and other heavy init.
  • Conditional annotations drive auto-config; understand @ConditionalOnXxx when a feature is unexpectedly enabled or disabled.
  • Avoid long blocking work in Runners; use async or lazy init for expensive operations.
  • Use spring.main.lazy-initialization=true cautiously; it defers bean creation but can hide startup issues and shift cost to first request.

What's Next

See IOC & Bean Lifecycle, Config Priority. See AOP for bean proxy creation during startup.