keepcalculator

06 Numbers and Powers

Spring Boot Under the Hood: How It Builds the Container

Card No. 06·37902 · Updated · Standard formula, shown below

Spring Boot assembles the application context (the “container”) by first determining what kind of web application you have, then creating an appropriate ApplicationContext, and finally calling a chain of auto-configuration classes that conditionally register beans based on your classpath, properties, and existing beans. The entire process is driven by the SpringApplication class, which orchestrates environment preparation, bean definition loading, and context refresh. This article explains each step in that assembly, from the initial run call to the fully initialized context.

Background: Why Spring Boot Exists

Traditional Spring applications required developers to manually wire every bean in XML or Java configuration, declare component scans, and choose which libraries to integrate. Spring Boot was created to remove that boilerplate by providing auto-configuration: a set of pre-built configuration classes that activate only when certain conditions are met. For example, if spring-boot-starter-web is on the classpath, Spring Boot assumes you want a servlet web application and configures a DispatcherServlet, an embedded Tomcat server, and basic MVC settings. This conditional logic is what makes the container assembly feel “automatic” — but under the hood it is a deterministic sequence of steps.

The Entry Point: SpringApplication.run()

When you call SpringApplication.run(YourApplication.class, args), the static method creates a new SpringApplication instance and then calls its run() method. The constructor performs several key tasks:

  • It infers the application type from the classpath: WebApplicationType.SERVLET if javax.servlet.Servlet is present, REACTIVE if org.springframework.web.reactive.DispatcherHandler is present, or NONE for a plain non-web app.
  • It reads the spring.factories file from all JARs to find ApplicationContextInitializer and ApplicationListener implementations.
  • It determines the main application class by examining the stack trace (the class that called main).

The run() method then starts a StopWatch, creates a SpringApplicationRunListeners object (which broadcasts lifecycle events), and prepares the environment. The environment holds all property sources: system properties, environment variables, and your application.properties or application.yml files.

Preparing the Environment

Before the container exists, Spring Boot must know what configuration to use. It creates a ConfigurableEnvironment (e.g., StandardServletEnvironment for web apps) and populates it with property sources in a specific order. The most important steps are:

  1. Add default property sources (system properties and environment variables).
  2. Load application.properties or application.yml from the classpath, configurable via spring.config.name and spring.config.location.
  3. Process profile-specific files like application-dev.properties if the dev profile is active.
  4. Bind the environment to the SpringApplication object itself (so you can set properties like spring.main.banner-mode).

This environment is later passed to the context so that beans can read properties via @Value or @ConfigurationProperties.

Creating the ApplicationContext

With the environment ready, Spring Boot creates the actual container. The type of context depends on the detected web application type:

  • Servlet web app: AnnotationConfigServletWebServerApplicationContext
  • Reactive web app: AnnotationConfigReactiveWebServerApplicationContext
  • Non-web app: AnnotationConfigApplicationContext

These classes extend the standard GenericApplicationContext but add web-specific features like the embedded server factory. The context is created with the main application class as a configuration source, so Spring will process its annotations (@SpringBootApplication, @ComponentScan, etc.).

Auto-Configuration: The Heart of the Assembly

Auto-configuration is implemented via @EnableAutoConfiguration, which is part of @SpringBootApplication. This annotation imports AutoConfigurationImportSelector, a class that reads the spring.factories file and finds all entries under org.springframework.boot.autoconfigure.EnableAutoConfiguration. That list contains hundreds of configuration classes, but only a few will actually be applied because each is annotated with @Conditional annotations. Common conditions include:

  • @ConditionalOnClass — activates only if a specific class is on the classpath (e.g., Tomcat).
  • @ConditionalOnMissingBean — activates only if you haven’t already defined a bean of that type.
  • @ConditionalOnProperty — activates based on a property value (e.g., spring.datasource.enable).
  • @ConditionalOnWebApplication — activates only in a web context.

For example, DataSourceAutoConfiguration is conditional on javax.sql.DataSource being present and on the absence of a user-defined DataSource bean. If those conditions hold, it configures a default HikariCP data source using properties like spring.datasource.url.

Bean Definition Loading and Registration

Once the context is created, Spring Boot registers the main configuration class and then processes all auto-configuration classes. The AutoConfigurationImportSelector returns the class names, and the context reads them as additional configuration classes. At this point, the standard Spring container machinery takes over:

  1. The ConfigurationClassPostProcessor parses each configuration class, looking for @Bean methods, @ComponentScan, and @Import annotations.
  2. Component scanning finds all classes annotated with @Component, @Service, @Repository, etc., in the packages you specified.
  3. Bean definitions are registered in the BeanDefinitionRegistry.
  4. Any BeanFactoryPostProcessor beans are executed (e.g., property placeholders are resolved).

Only after all bean definitions are registered does the context begin instantiating beans. This two-phase approach (definition first, then instantiation) is what allows Spring to resolve circular dependencies and apply post-processors correctly.

Context Refresh and the Embedded Server

The final step is calling refresh() on the context. This is a standard Spring method that completes the container lifecycle:

  • Instantiate all remaining singleton beans (lazy beans are skipped until requested).
  • Invoke BeanPostProcessor hooks (e.g., @Autowired injection, @PostConstruct).
  • Initialize the MessageSource for i18n.
  • Start the embedded web server if this is a web context. For Tomcat, Spring Boot creates a TomcatServletWebServerFactory that builds a Tomcat instance on the configured port (default 8080).
  • Publish the ContextRefreshedEvent and then ApplicationReadyEvent.

After refresh() returns, the SpringApplication.run() method prints the startup banner and returns the fully assembled ApplicationContext. Your CommandLineRunner or ApplicationRunner beans execute at this point, and the application is ready to serve requests.

Common Misconceptions and Troubleshooting

A frequent mistake is thinking that auto-configuration is a single monolithic block. In reality, each auto-configuration class is independent and ordered via @AutoConfigureBefore, @AutoConfigureAfter, and @AutoConfigureOrder. If you see a bean not being created, it’s usually because a condition failed. To debug, set debug=true in your properties; Spring Boot will log a report of all auto-configuration decisions, showing which conditions matched and which did not. Another common issue is defining your own bean that accidentally prevents an auto-configuration from applying (e.g., a custom ObjectMapper that blocks the Jackson auto-config). In that case, you can exclude the auto-configuration class via @SpringBootApplication(exclude = JacksonAutoConfiguration.class).

Frequently Asked Questions

What is the difference between ApplicationContext and BeanFactory?

BeanFactory is the low-level container that manages beans. ApplicationContext extends it and adds enterprise features like event publishing, internationalization, and web server integration. Spring Boot always creates an ApplicationContext subclass, never a bare BeanFactory.

How does Spring Boot decide between Tomcat, Jetty, and Undertow?

It doesn’t decide on its own. The embedded server is chosen by which starter you include. If you add spring-boot-starter-web, it pulls in Tomcat by default. To use Jetty, exclude Tomcat and add spring-boot-starter-jetty; the auto-configuration will detect the new server class and configure it.

Can I see which auto-configurations are active?

Yes. Set debug=true in application.properties and run your app. The console will print a detailed report titled “CONDITIONS EVALUATION REPORT” that lists every auto-configuration class and whether it matched or not.

What happens if I define a bean that conflicts with an auto-configuration?

Most auto-configurations use @ConditionalOnMissingBean, so your bean will win and the auto-configuration will back off. If two beans of the same type exist, you’ll get a NoUniqueBeanDefinitionException when injecting. You can resolve it by marking one as @Primary or using @Qualifier.

Is the Spring Boot container the same as a servlet container?

No. The Spring container is the ApplicationContext that manages your beans. The servlet container (Tomcat, Jetty) is a separate runtime that handles HTTP requests. Spring Boot embeds the servlet container inside the application process, but they are distinct components.

Why does my application start so slowly?

Startup time is often dominated by component scanning and auto-configuration evaluation. You can speed it up by limiting @ComponentScan base packages, using @ConditionalOnClass to avoid loading unnecessary libraries, and enabling lazy initialization with spring.main.lazy-initialization=true.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →