How a Spring Boot Application Works (Under the Hood)

1) Launch & bootstrap

  • main() calls SpringApplication.run(App.class, args).
  • SpringApplication decides the app type (Servlet/WebFlux/none), sets up Bootstrap logging, loads ApplicationContext type, banner, listeners, and default converters.

2) Create the ApplicationContext

  • It creates an IoC container (AnnotationConfigApplicationContext for non-web, ServletWebServerApplicationContext for MVC, or ReactiveWebServerApplicationContext for WebFlux).
  • Environment (profiles, properties) is prepared: command-line args, application.yml/properties, OS env vars, system props, profile-specific files (application-dev.yml), and any PropertySource you add.

3) Auto-configuration & component scanning

  • @SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan.
  • Component scan finds your @Component, @Service, @Repository, @Controller/@RestController, @Configuration classes.
  • Auto-configuration imports a curated set of @Configuration classes based on the classpath & Environment (e.g., if spring-boot-starter-web is present, it configures Spring MVC, Jackson, an embedded server, etc.).
    • Runs via conditions: @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty, etc.
    • Since Spring Boot 3, auto-config classes are listed in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (replacing old spring.factories approach).

4) Bean creation & lifecycle

  • The container registers bean definitions (from your app + auto-config) and then instantiates beans in dependency order.
  • Lifecycle hooks apply: constructor injection → @PostConstruct (or InitializingBean.afterPropertiesSet) → BeanPostProcessors (AOP proxies created here) → ready.
  • If a bean fails its condition, Boot backs off so your custom bean wins (@ConditionalOnMissingBean).

5) Externalized configuration binding

  • Boot binds properties to strongly typed configs using @ConfigurationProperties (via the Binder API) and also to @Value fields.
  • Relaxed binding: my.service-timeout, my.serviceTimeout, MY_SERVICE_TIMEOUT all map to my.service-timeout.

Short example:

@ConfigurationProperties(prefix="app.greeting")
public record GreetingProps(String message, int repeat) {}

6) Web stack setup (Servlet or Reactive)

Servlet/MVC path
13. Boot starts an embedded server (Tomcat/Jetty/Undertow) via ServletWebServerFactory.
14. Registers DispatcherServlet, Filters, Servlets, and MVC infrastructure (HandlerMappings, HandlerAdapters, MessageConverters, ExceptionResolvers).
15. An HTTP request flows:

  • Server → Filter chain → DispatcherServlet
  • Choose handler (controller) via HandlerMapping
  • Invoke controller method (argument resolvers bind path/query/body)
  • Return value written via HttpMessageConverters (e.g., JSON via Jackson)
  • Exception handling via @ControllerAdvice / resolvers
  • Response → Filters → client

Reactive/WebFlux path
16. Starts Netty (via Reactor Netty), sets up DispatcherHandler, HandlerMappings/Adapters/ResultHandlers, and encoders/decoders for reactive types (Mono/Flux).

7) Actuator, metrics, health

  • If spring-boot-starter-actuator is present, Boot auto-configures /actuator endpoints (health, metrics, info), Micrometer registry, and health contributors (DB, disk, etc.). Secure & expose endpoints based on properties.

8) Application events & runners

  • Boot publishes lifecycle events (ApplicationStarting → EnvironmentPrepared → Prepared → Started → Ready).
  • Your CommandLineRunner / ApplicationRunner beans run after context is ready (great for seeding data, warmups).
@SpringBootApplication
@EnableConfigurationProperties(GreetingProps.class)
public class App {
  public static void main(String[] args) { SpringApplication.run(App.class, args); }

  @Bean CommandLineRunner runner(GreetingProps p) {
    return args -> { for (int i=0; i<p.repeat(); i++) System.out.println(p.message()); };
  }
}

9) Packaging & execution model

  • Boot builds a fat/uber JAR with a custom Spring Boot Loader; you can java -jar app.jar. It sets up a layered classloader so dependencies and app classes are isolated (helps with Docker layering too).

10) Profiles & environment-specific beans

  • Use @Profile("dev") / spring.profiles.active=prod to switch beans and properties per environment. Conditions can also check Cloud platforms, presence of libs, or flags.

11) Security (if added)

  • Adding spring-boot-starter-security auto-configures a security filter chain with sensible defaults (CSRF, basic login) and you customize via SecurityFilterChain beans.

12) AOT & native images (optional)

  • With Spring AOT & GraalVM Native, Boot can generate ahead-of-time hints (reflection, proxies) to build native binaries for super-fast startup & low memory.

TL;DR sequence

main() → SpringApplication prepares env → create ApplicationContext → component scan + conditional auto-config → bind externalized config → create/proxy beans → start embedded server (or Netty) → register dispatch layer → run runners → Ready.


Back to blog

Leave a comment