Home / Spring Boot / Spring Boot Internals / How Spring Boot Starts: SpringApplication.run() Step by Step

How Spring Boot Starts: SpringApplication.run() Step by Step

Every Spring Boot application starts with one line: SpringApplication.run(). Behind it are about ten well-defined phases, and each phase publishes an event that you can hook into. If you understand this workflow, you can debug slow startups, write EnvironmentPostProcessors, custom listeners and failure analyzers with confidence.

The big picture

SpringApplication.run(App.class, args)
 │
 ├─ [A] new SpringApplication(App.class)
 │      ├─ deduce application type: SERVLET / REACTIVE / NONE (look at the classpath)
 │      ├─ load ApplicationContextInitializer + ApplicationListener (META-INF/spring.factories)
 │      └─ deduce the main class
 │
 └─ [B] run(args)
        1. StopWatch starts, BootstrapContext created
        2. RunListeners  ─► event: ApplicationStartingEvent
        3. prepareEnvironment
              ├─ create Environment (system props, env vars, command line args)
              ├─ event: ApplicationEnvironmentPreparedEvent
              │     └─ EnvironmentPostProcessors run: application.yml, profiles, spring.config.import
              └─ bind spring.main.* properties to the SpringApplication
        4. print the banner
        5. createApplicationContext()   (AnnotationConfigServletWebServerApplicationContext)
        6. prepareContext
              ├─ set Environment, run ApplicationContextInitializers
              ├─ event: ApplicationContextInitializedEvent
              ├─ register the main class as a bean definition
              └─ event: ApplicationPreparedEvent
        7. refreshContext  ──►  context.refresh()   (see the next page for the 12 steps)
              ├─ scan components, run auto-configuration, create all singleton beans
              └─ create and START the embedded Tomcat
        8. event: ApplicationStartedEvent
        9. callRunners: ApplicationRunner and CommandLineRunner beans (ordered)
       10. event: ApplicationReadyEvent   ◄── the application is now ready for traffic
       (on any exception) event: ApplicationFailedEvent + FailureAnalyzers print a friendly message

Events in order and what you can do in each

EventWhenTypical use
ApplicationStartingEventVery early, before Environment and context existRegister early logging or system setup. Only listeners registered in spring.factories or via SpringApplication see it
ApplicationEnvironmentPreparedEventEnvironment ready, context not createdAdd or change property sources (EnvironmentPostProcessor)
ApplicationContextInitializedEventContext created, no bean definitions loadedRarely used
ApplicationPreparedEventBean definitions loaded, beans not createdChange bean definitions programmatically
ContextRefreshedEventContext refreshed (this is a Spring event)Runs also for child contexts, be careful
WebServerInitializedEventWeb server startedRead the real port when server.port=0
ApplicationStartedEventContext refreshed, runners not called yetWarm-up tasks that should run before runners
AvailabilityChangeEvent (LIVENESS)Application is aliveHealth probes
ApplicationReadyEventRunners finished, app is readyStart background consumers, log 'ready', prime caches
AvailabilityChangeEvent (READINESS)Ready to accept trafficKubernetes readiness
ApplicationFailedEventStartup failedSend an alert, clean up

Hooking into the phases: examples

// 1. Listener for ApplicationReadyEvent (works as a normal bean)
@Component
public class ReadyLogger {
    private static final Logger log = LoggerFactory.getLogger(ReadyLogger.class);

    @EventListener(ApplicationReadyEvent.class)
    public void onReady(ApplicationReadyEvent event) {
        log.info("Application ready in {}", event.getTimeTaken());
    }

    @EventListener
    public void onWebServer(WebServerInitializedEvent event) {
        log.info("Web server is listening on port {}", event.getWebServer().getPort());
    }
}

// 2. Runners with an order
@Component @Order(1)
class FirstRunner implements ApplicationRunner {
    public void run(ApplicationArguments args) { System.out.println("runner 1, option names=" + args.getOptionNames()); }
}
@Component @Order(2)
class SecondRunner implements CommandLineRunner {
    public void run(String... args) { System.out.println("runner 2, raw args=" + Arrays.toString(args)); }
}

EnvironmentPostProcessor: add configuration before beans exist

This runs in the phase after the Environment is prepared. Use it to load secrets, decrypt values or compute properties. It must be registered in META-INF/spring/org.springframework.boot.EnvironmentPostProcessor.imports (Boot 3.x older versions used spring.factories).

public class SecretsEnvironmentPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication application) {
        Map<String, Object> secrets = new HashMap<>();
        secrets.put("app.api-key", System.getenv().getOrDefault("MY_API_KEY", "dev-key"));
        // added with the HIGHEST priority
        env.getPropertySources().addFirst(new MapPropertySource("secrets", secrets));
    }
}
// file: META-INF/spring/org.springframework.boot.EnvironmentPostProcessor.imports
// com.example.SecretsEnvironmentPostProcessor

Customize SpringApplication

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(DemoApplication.class);
    app.setBannerMode(Banner.Mode.OFF);
    app.setAdditionalProfiles("metrics");
    app.setLazyInitialization(false);
    app.addListeners(new MyStartingListener());
    app.run(args);
}
// Fluent style:
// new SpringApplicationBuilder(DemoApplication.class).web(WebApplicationType.NONE).run(args);

Find out why startup is slow

SpringApplication app = new SpringApplication(DemoApplication.class);
app.setApplicationStartup(new BufferingApplicationStartup(4096));   // records every startup step
app.run(args);
// then: management.endpoints.web.exposure.include=startup
//       curl -X POST localhost:8080/actuator/startup   (JSON timeline of bean creation)

Friendly failure messages: FailureAnalyzer

public class MissingApiKeyFailureAnalyzer extends AbstractFailureAnalyzer<MissingApiKeyException> {
    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, MissingApiKeyException cause) {
        return new FailureAnalysis("The property app.api-key is missing.",
                "Set the environment variable MY_API_KEY or the property app.api-key.", cause);
    }
}
// Register in META-INF/spring.factories:
// org.springframework.boot.diagnostics.FailureAnalyzer=com.example.MissingApiKeyFailureAnalyzer

Example of the built-in analyzers: the well known message 'APPLICATION FAILED TO START, Description: ... Action: ...' for Port 8080 was already in use is produced by a FailureAnalyzer.

Common mistake: Doing heavy work in a constructor or @PostConstruct of many beans (for example calling remote services). All startup work runs on the main thread, one bean after another, and delays readiness. Move slow warm-up work to an ApplicationReadyEvent listener or an async task.

Interview questions

What is the difference between ApplicationStartedEvent and ApplicationReadyEvent?

Started is published after the context is refreshed but before the runners run. Ready is published after all ApplicationRunner and CommandLineRunner beans finished, so it is the real signal that the application is ready.

Where do I put code that must run before any bean is created?

In an EnvironmentPostProcessor, an ApplicationContextInitializer or a BeanFactoryPostProcessor, depending on what you need to change.

How does Spring Boot decide it is a web application?

By the classpath: if Servlet API and Spring MVC are present it is SERVLET, if Spring WebFlux is present and MVC is not it is REACTIVE, otherwise NONE. You can override it with spring.main.web-application-type.