DEV Community

Cover image for Part 1: What Really Happens When You Call SpringApplication.run()
Silver_dev
Silver_dev

Posted on

Part 1: What Really Happens When You Call SpringApplication.run()

1.1. The main() Entry Point — Anatomy of the Startup

Every Spring Boot application starts the same way:

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
Enter fullscreen mode Exit fullscreen mode

That single line sets the entire machinery in motion.

1.2. Static run() vs. a SpringApplication Instance

There are two ways to launch an application:

Approach 1 — the static method (95% of cases):

SpringApplication.run(MyApplication.class, args);
Enter fullscreen mode Exit fullscreen mode

Under the hood, it does this:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return new SpringApplication(primarySource).run(args);
}
Enter fullscreen mode Exit fullscreen mode

So a SpringApplication instance is created first, and then its instance method run() is called. This is important to understand: all configuration happens in the constructor, while run() is pure execution.

Approach 2 — manual instantiation (when you need customization before the run):

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MyApplication.class);
    app.setBannerMode(Banner.Mode.OFF);
    app.setAdditionalProfiles("dev");
    app.setWebApplicationType(WebApplicationType.SERVLET);
    app.run(args);
}
Enter fullscreen mode Exit fullscreen mode

This is exactly why a separate class exists — SpringApplicationBuilder — for fluent configuration:

new SpringApplicationBuilder(MyApplication.class)
    .profiles("dev")
    .bannerMode(Banner.Mode.OFF)
    .web(WebApplicationType.SERVLET)
    .run(args);
Enter fullscreen mode Exit fullscreen mode

SpringApplicationBuilder is especially useful when you need to build a parent-child context hierarchy:

new SpringApplicationBuilder(ParentConfig.class)
    .child(ChildConfig.class)
    .run(args);
Enter fullscreen mode Exit fullscreen mode

This approach is used in Spring Cloud (the bootstrap context) and in testing scenarios.

1.3. The SpringApplication Constructor — What Happens Before run()

The constructor does three key things:

1. Deducing the application type (WebApplicationType)

private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    // ... checks for SERVLET and NONE
}
Enter fullscreen mode Exit fullscreen mode

Spring Boot scans the classpath:

  • DispatcherServlet present (Spring MVC) → SERVLET
  • DispatcherHandler present (WebFlux) without MVC → REACTIVE
  • No web classes → NONE

2. Loading the ApplicationContextInitializers and ApplicationListeners

Via SpringFactoriesLoader, it reads META-INF/spring.factories:

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
...
Enter fullscreen mode Exit fullscreen mode
Aspect ApplicationContextInitializer ApplicationListener
Pattern Initialization strategy (Template Method) Observer
Main purpose Configure / modify the context and environment React to what's happening with the application
Timing Exactly once: before refresh() (before beans are created) Multiple times: at startup, at runtime, on shutdown
Access to beans No (beans don't exist yet) Yes (if you listen to later events or work inside a bean)
Typical example Pull secrets from Vault into the Environment Send a Telegram alert on ApplicationReadyEvent

3. Setting the primary sources

MyApplication.class is stored as primarySources — the root @Configuration class from which component scanning will start.

1.4. The Instance run() Method — a Step-by-Step Breakdown

Here is the skeleton of the run(String... args) method (Spring Boot 3.x):

public ConfigurableApplicationContext run(String... args) {
    // 1. Create the BootstrapContext
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;

    // 2. Headless mode (java.awt.headless)
    configureHeadlessProperty();

    // 3. Get the run listeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        // 4. Prepare the Environment
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
            bootstrapContext, applicationArguments);

        // 5. Print the banner
        Banner printedBanner = printBanner(environment);

        // 6. Create the ApplicationContext
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);

        // 7. Prepare the context
        prepareContext(bootstrapContext, context, environment, listeners,
            applicationArguments, printedBanner);

        // 8. Refresh — the key stage
        refreshContext(context);

        // 9. afterRefresh (hook)
        afterRefresh(context, applicationArguments);

        // 10. Publish ApplicationStartedEvent
        listeners.started(context, timeTakenToStartup);

        // 11. Call ApplicationRunner / CommandLineRunner
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

    // 12. Publish ApplicationReadyEvent
    listeners.ready(context, timeTakenToReady);
    return context;
}
Enter fullscreen mode Exit fullscreen mode

Step 1: DefaultBootstrapContext

BootstrapContext is a container for objects that are available before the ApplicationContext is created. It lives from the start of run() until the context is ready. It is used for registering early beans, such as the EnvironmentPostProcessor.

Step 2: configureHeadlessProperty()

private void configureHeadlessProperty() {
    System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
        System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
            Boolean.toString(this.headless)));
}
Enter fullscreen mode Exit fullscreen mode

Sets java.awt.headless=true by default. This matters for server-side applications — it disables AWT GUI components.

Step 3: SpringApplicationRunListeners — the heart of the event mechanism

This is the SPI interface for startup lifecycle listeners:

public interface SpringApplicationRunListener {
    void starting(ConfigurableBootstrapContext bootstrapContext);
    void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
        ConfigurableEnvironment environment);
    void contextPrepared(ConfigurableApplicationContext context);
    void contextLoaded(ConfigurableApplicationContext context);
    void started(ConfigurableApplicationContext context, Duration timeTaken);
    void ready(ConfigurableApplicationContext context, Duration timeTaken);
    void failed(ConfigurableApplicationContext context, Throwable exception);
}
Enter fullscreen mode Exit fullscreen mode

It is loaded via SpringFactoriesLoader from META-INF/spring.factories. Spring Boot ships a single implementation — EventPublishingRunListener.

EventPublishingRunListener is a bridge between SpringApplicationRunListener and ApplicationEvent. It translates every lifecycle callback into the corresponding event.

Callback Event
starting() ApplicationStartingEvent
environmentPrepared() ApplicationEnvironmentPreparedEvent
contextPrepared() ApplicationContextInitializedEvent
contextLoaded() ApplicationPreparedEvent
started() ApplicationStartedEvent
ready() ApplicationReadyEvent
failed() ApplicationFailedEvent

The complete sequence of events:

Event When Context available?
ApplicationStartingEvent Before the Environment and Context exist No
ApplicationEnvironmentPreparedEvent Environment is ready, Context is not yet No
ApplicationContextInitializedEvent Context created, beans not yet loaded Yes (empty)
ApplicationPreparedEvent Beans loaded, refresh() not yet called Yes (not refreshed)
ApplicationStartedEvent refresh() done, runners not yet invoked Yes (refreshed)
AvailabilityChangeEvent (Liveness) Right after ApplicationStartedEvent Yes
ApplicationReadyEvent All runners have been executed Yes (refreshed)
AvailabilityChangeEvent (Readiness) Right after ApplicationReadyEvent Yes
ApplicationFailedEvent On any unhandled exception Partially

Step 4: prepareEnvironment() — preparing the environment

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ConfigurableBootstrapContext bootstrapContext,
        ApplicationArguments applicationArguments) {

    // 1. Create or reuse the Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();

    // 2. Configure it: command line args, profiles
    configureEnvironment(environment, applicationArguments.getSourceArgs());

    // 3. Attach the ConfigurationPropertySources
    ConfigurationPropertySources.attach(environment);

    // 4. Publish ApplicationEnvironmentPreparedEvent
    listeners.environmentPrepared(bootstrapContext, environment);

    // 5. Bind spring.main.*
    DefaultPropertiesPropertySource.moveToEnd(environment);
    bindToSpringApplication(environment);

    // 6. Convert the Environment if necessary
    if (!this.isCustomEnvironment) {
        environment = convertEnvironment(environment);
    }

    ConfigurationPropertySources.attach(environment);
    return environment;
}
Enter fullscreen mode Exit fullscreen mode

Key points:

  • getOrCreateEnvironment() — creates StandardServletEnvironment / StandardReactiveEnvironment / StandardEnvironment
  • configureEnvironment() — adds a CommandLinePropertySource and sets the active profiles
  • listeners.environmentPrepared() — this is where the EnvironmentPostProcessors kick in (e.g., loading application.yml)

Step 5: printBanner() — printing the banner

private Banner printBanner(ConfigurableEnvironment environment) {
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }
    ResourceLoader resourceLoader = (this.resourceLoader != null)
        ? this.resourceLoader : new DefaultResourceLoader(null);
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
        resourceLoader, this.banner);

    if (this.bannerMode == Banner.Mode.LOG) {
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
Enter fullscreen mode Exit fullscreen mode
  • A banner.txt file in src/main/resources/ is picked up automatically
  • spring.banner.location — specify a custom file path
  • spring.main.banner-mode=off — disable the banner
  • Spring Boot 3.0.0 M2+: image banner support (PNG/JPEG/GIF) was removed; only banner.txt remains

Variables available in the banner: ${spring-boot.version}, ${application.version}, ${application.formatted-version}

Step 6: createApplicationContext() — choosing the context type

protected ConfigurableApplicationContext createApplicationContext() {
    return this.applicationContextFactory.create(this.webApplicationType);
}
Enter fullscreen mode Exit fullscreen mode

Spring Boot 3 uses an ApplicationContextFactory (an enum-like factory):

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
    try {
        switch (webApplicationType) {
            case SERVLET:
                return new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE:
                return new AnnotationConfigReactiveWebServerApplicationContext();
            default:
                return new AnnotationConfigApplicationContext();
        }
    } catch (Exception ex) {
        throw new IllegalStateException("Unable create a default ApplicationContext instance, "
            + "you may need a custom ApplicationContextFactory", ex);
    }
};
Enter fullscreen mode Exit fullscreen mode

In Spring Boot 4, ApplicationContextFactory has been removed. Instead, a modular system is used — each module (spring-boot-webmvc, spring-boot-webflux) registers its own context via module dependencies. AbstractApplicationContextFactory is marked as @Deprecated(since="6.0", forRemoval=true).

Step 7: prepareContext() — preparing the context

private void prepareContext(DefaultBootstrapContext bootstrapContext,
        ConfigurableApplicationContext context,
        ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments,
        Banner printedBanner) {

    // 1. Set the Environment
    context.setEnvironment(environment);

    // 2. postProcessApplicationContext (bean name generator, resource loader)
    postProcessApplicationContext(context);

    // 3. Apply the ApplicationContextInitializers
    applyInitializers(context);

    // 4. Publish ApplicationContextInitializedEvent
    listeners.contextPrepared(context);

    // 5. Register singleton beans: springApplicationArguments, springBootBanner
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory dlbf) {
        dlbf.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(
            new LazyInitializationBeanFactoryPostProcessor());
    }

    // 6. Load all the sources
    Set<Object> sources = getAllSources();
    load(context, sources.toArray(new Object[0]));

    // 7. Publish ApplicationPreparedEvent
    listeners.contextLoaded(context);
}
Enter fullscreen mode Exit fullscreen mode

The key moment is applyInitializers(): this is where all ApplicationContextInitializers loaded from spring.factories get invoked. It's the mechanism for customizing the context before any beans are loaded.

Step 8: refreshContext() — starting the Spring container

private void refreshContext(ConfigurableApplicationContext context) {
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        } catch (AccessControlException ex) {
            // Not allowed in some environments
        }
    }
    refresh(context);
}
Enter fullscreen mode Exit fullscreen mode

The refresh() method here is AbstractApplicationContext.refresh() from Spring Framework. This is where:

  • All singleton beans are created
  • BeanFactoryPostProcessors run → ConfigurationClassPostProcessorprocesses @Configuration
  • AutoConfigurationImportSelector runs → auto-configuration
  • BeanPostProcessors run
  • The embedded web server starts

Step 9: afterRefresh() — a hook

protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
}
Enter fullscreen mode Exit fullscreen mode

An empty hook meant to be overridden in subclasses.

Step 10: listeners.started()ApplicationStartedEvent

ApplicationStartedEvent and AvailabilityChangeEvent(LivenessState.CORRECT) are published. From this moment on, the application is considered started, but it is not yet ready to serve traffic.

Step 11: callRunners()ApplicationRunner and CommandLineRunner

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner applicationRunner) {
            callRunner(applicationRunner, args);
        }
        if (runner instanceof CommandLineRunner commandLineRunner) {
            callRunner(commandLineRunner, args);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
  • ApplicationRunner — receives ApplicationArguments (parsed)
  • CommandLineRunner — receives a raw String[]
  • They are sorted via @Order / Ordered

Step 12: listeners.ready()ApplicationReadyEvent

ApplicationReadyEvent and AvailabilityChangeEvent(ReadinessState.ACCEPTING_TRAFFIC) are published. From this point on, the application is ready to accept traffic.

1.5. Error Handling — handleRunFailure()

private void handleRunFailure(ConfigurableApplicationContext context,
        Throwable exception, SpringApplicationRunListeners listeners) {
    try {
        try {
            handleExitCode(context, exception);
            if (listeners != null) {
                listeners.failed(context, exception);
            }
        } finally {
            reportFailure(getExceptionReporters(context), exception);
            if (context != null) {
                context.close();
            }
        }
    } catch (Exception ex) {
        logger.warn("Unable to close ApplicationContext", ex);
    }
    ReflectionUtils.rethrowRuntimeException(exception);
}
Enter fullscreen mode Exit fullscreen mode
  • handleExitCode() — determines the exit code via ExitCodeExceptionMapper
  • listeners.failed() — publishes ApplicationFailedEvent
  • FailureAnalyzer — attempts to produce a human-readable description of the failure
  • The context is closed

1.6. Startup Sequence Diagram

main()
  │
  ├─ new SpringApplication(primarySource)
  │     ├─ deduceWebApplicationType()          → SERVLET / REACTIVE / NONE
  │     ├─ load SpringFactories (Initializers, Listeners)
  │     └─ set primarySources
  │
  └─ .run(args)
        │
        ├─ createBootstrapContext()
        ├─ configureHeadlessProperty()
        ├─ getRunListeners()                    → EventPublishingRunListener
        ├─ listeners.starting()                 → ApplicationStartingEvent
        │
        ├─ prepareEnvironment()
        │     ├─ getOrCreateEnvironment()       → StandardServletEnvironment
        │     ├─ configureEnvironment()         → CommandLinePropertySource, profiles
        │     ├─ ConfigurationPropertySources.attach()
        │     └─ listeners.environmentPrepared()→ ApplicationEnvironmentPreparedEvent
        │           └─ EnvironmentPostProcessor → ConfigDataEnvironmentPostProcessor
        │                 └─ loading application.yml/properties
        │
        ├─ printBanner()                        → banner.txt → System.out
        │
        ├─ createApplicationContext()
        │     └─ ApplicationContextFactory      → AnnotationConfigServletWebServerApplicationContext
        │
        ├─ prepareContext()
        │     ├─ setEnvironment()
        │     ├─ postProcessApplicationContext()
        │     ├─ applyInitializers()            → ApplicationContextInitializer
        │     ├─ listeners.contextPrepared()    → ApplicationContextInitializedEvent
        │     ├─ registerSingleton(springApplicationArguments)
        │     ├─ registerSingleton(springBootBanner)
        │     ├─ load(sources)                  → BeanDefinitionLoader
        │     └─ listeners.contextLoaded()      → ApplicationPreparedEvent
        │
        ├─ refreshContext()
        │     ├─ registerShutdownHook()
        │     └─ AbstractApplicationContext.refresh()
        │           ├─ invokeBeanFactoryPostProcessors()
        │           │     └─ ConfigurationClassPostProcessor → @Configuration, @Bean
        │           │     └─ AutoConfigurationImportSelector → auto-configuration
        │           ├─ registerBeanPostProcessors()
        │           ├─ initMessageSource()
        │           ├─ initApplicationEventMulticaster()
        │           ├─ onRefresh()
        │           │     └─ ServletWebServerApplicationContext.createWebServer()
        │           │           └─ TomcatServletWebServerFactory → Tomcat.start()
        │           └─ finishRefresh()
        │
        ├─ afterRefresh()                       → hook
        ├─ listeners.started()                  → ApplicationStartedEvent
        │                                          AvailabilityChangeEvent(Liveness)
        │
        ├─ callRunners()
        │     ├─ ApplicationRunner
        │     └─ CommandLineRunner
        │
        └─ listeners.ready()                    → ApplicationReadyEvent
                                                   AvailabilityChangeEvent(Readiness)
Enter fullscreen mode Exit fullscreen mode

1.7. Spring Boot 3 vs Spring Boot 4 — Key Bootstrap Differences

Aspect Spring Boot 3.x Spring Boot 4.x
ApplicationContextFactory Enum-like DEFAULT factory, picks a context based on WebApplicationType Removed. The context is determined by module dependencies (spring-boot-webmvc / spring-boot-webflux)
spring-boot-autoconfigure A single monolithic JAR (6.2 MB) 47 modular JARs, one per technology
Starters spring-boot-starter-web spring-boot-starter-webmvc (renamed)
spring.factories SPI for auto-configuration META-INF/spring/...AutoConfiguration.imports (already in 3.x, mandatory in 4.x)
Undertow Supported Removed (incompatible with Servlet 6.1)
Java baseline Java 17+ Java 21+
Spring Framework 6.x 7.0
Jackson Jackson 2.x Jackson 3.x (namespace changes)
Banner (image) PNG/JPEG/GIF removed since 3.0 M2 banner.txt only

Key Takeaways

  • SpringApplication.run() is a facade. All the real work happens in the instance run() method.
  • The SpringApplication constructor deduces the WebApplicationType from the classpath and loads the SPIs.
  • SpringApplicationRunListener is an extension point for hooking into the lifecycle. EventPublishingRunListener translates those calls into ApplicationEvents.
  • The Environment is prepared before the ApplicationContext — that's why EnvironmentPostProcessors can add property sources before any beans exist.
  • refreshContext() is the most important step. This is where all the beans are created, @Configuration classes are processed, and Tomcat/Netty starts.
  • ApplicationStartedEventApplicationReadyEvent. The former means the context is up; the latter means the runners have executed and the application is ready to accept traffic.
  • Spring Boot 4 is modular. ApplicationContextFactory is gone — the context is determined by your dependencies.

Top comments (0)