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.SERVLETifjavax.servlet.Servletis present,REACTIVEiforg.springframework.web.reactive.DispatcherHandleris present, orNONEfor a plain non-web app. - It reads the
spring.factoriesfile from all JARs to findApplicationContextInitializerandApplicationListenerimplementations. - 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:
- Add default property sources (system properties and environment variables).
- Load
application.propertiesorapplication.ymlfrom the classpath, configurable viaspring.config.nameandspring.config.location. - Process profile-specific files like
application-dev.propertiesif thedevprofile is active. - Bind the environment to the
SpringApplicationobject itself (so you can set properties likespring.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:
- The
ConfigurationClassPostProcessorparses each configuration class, looking for@Beanmethods,@ComponentScan, and@Importannotations. - Component scanning finds all classes annotated with
@Component,@Service,@Repository, etc., in the packages you specified. - Bean definitions are registered in the
BeanDefinitionRegistry. - Any
BeanFactoryPostProcessorbeans 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
BeanPostProcessorhooks (e.g.,@Autowiredinjection,@PostConstruct). - Initialize the
MessageSourcefor i18n. - Start the embedded web server if this is a web context. For Tomcat, Spring Boot creates a
TomcatServletWebServerFactorythat builds a Tomcat instance on the configured port (default 8080). - Publish the
ContextRefreshedEventand thenApplicationReadyEvent.
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
- Valheim Server Not Showing in Community List Fixes
- DisplayPort vs HDMI: Which Cable Should You Use?
- Soundbar vs 2.1 Speaker System: Which Is Better?
- Mouse Moving on Its Own? Causes & Fixes
- Slack Channels for Construction Projects: Setup Guide
- Surface Won’t Turn On: Quick Fixes1.8 h