🍃 Section 5 · Question #21

How SpringBootApplication works

Inspecting the definition of @SpringBootApplication reveals that it is composed of three foundational Spring annotations:


🟢 Junior Level

@SpringBootApplication is the primary composite meta-annotation that bootstraps any Spring Boot application. It is placed on the main entry-point class containing the public static void main(String[] args) method.

package com.example.shop;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ShopApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShopApplication.class, args);
    }
}

The 3 Core Meta-Annotations Inside @SpringBootApplication

Inspecting the definition of @SpringBootApplication reveals that it is composed of three foundational Spring annotations:

                       [@SpringBootApplication]
                                   │
           ┌───────────────────────┼───────────────────────┐
           ▼                       ▼                       ▼
   [@SpringBootConfiguration]  [@EnableAutoConfiguration]  [@ComponentScan]
   Declares class as a         Enables automatic bean      Scans current package &
   Spring Configuration        discovery based on          all subpackages for
   source & test anchor.       classpath JARs.             @Component / @Service.
  1. @SpringBootConfiguration (A specialized @Configuration):
    • Designates the class as a configuration source capable of declaring @Bean factory methods.
    • Acts as a unique marker for Spring Boot testing slices (@SpringBootTest, @WebMvcTest, @DataJpaTest) to automatically discover the root application configuration.
  2. @EnableAutoConfiguration:
    • Enables Spring Boot’s auto-configuration engine, which scans classpath libraries and configuration properties to automatically register pre-configured infrastructure beans (databases, security, web servers, Jackson).
  3. @ComponentScan:
    • Recursively scans the package of the declaring class and all its child subpackages for classes annotated with @Component, @Service, @Repository, and @Controller.

[!IMPORTANT] Root Package Placement Rule: Always place your @SpringBootApplication class in the root package of your application (e.g., com.example.shop). Because @ComponentScan defaults to scanning the declaring class’s package and subpackages, all controllers, services, and repositories located below the root will be discovered automatically.


🟡 Middle Level

Configuration Attributes of @SpringBootApplication

@SpringBootApplication acts as a facade, exposing aliases that forward attributes to its underlying composite annotations:

@SpringBootApplication(
    // Forwarded to @EnableAutoConfiguration: Excludes specific auto-configurations
    exclude = { DataSourceAutoConfiguration.class, SecurityAutoConfiguration.class },
    
    // Forwarded to @ComponentScan: Overrides default package scanning
    scanBasePackages = { "com.example.shop", "com.example.common" },
    
    // Type-safe package scanning using marker classes (refactoring safe)
    scanBasePackageClasses = { CommonMarker.class },
    
    // Forwarded to @SpringBootConfiguration: Toggles CGLIB proxying (Full vs Lite mode)
    proxyBeanMethods = true
)
public class ShopApplication { }

The SpringApplication.run() Execution Pipeline

When SpringApplication.run(ShopApplication.class, args) is invoked, Spring Boot coordinates a deterministic sequence of bootstrap steps:

1. Determine Application Type (SERVLET, REACTIVE, or NONE)
             │
             ▼
2. Prepare Environment (Loads application.yml, CLI arguments, OS environment variables, Active Profiles)
             │
             ▼
3. Instantiate ApplicationContext (e.g. AnnotationConfigServletWebServerApplicationContext)
             │
             ▼
4. Prepare Context (Applies ApplicationContextInitializers, registers main Application.class)
             │
             ▼
5. context.refresh() (Core Spring Engine: ComponentScan, BeanFactoryPostProcessors, onRefresh Web Server, Singletons)
             │
             ▼
6. Execute Runners (Invokes all CommandLineRunner and ApplicationRunner beans)
             │
             ▼
7. Publish ApplicationReadyEvent (Application is fully initialized and ready for production traffic)

When Does the Embedded Web Server (Tomcat) Start?

A common senior interview question is: “At what exact lifecycle stage is the embedded Tomcat/Jetty server started?”

The web server starts inside the context.refresh() method, specifically during the onRefresh() hook of ServletWebServerApplicationContext:

  1. Spring retrieves the configured ServletWebServerFactory bean (by default, TomcatServletWebServerFactory).
  2. It programmatically instantiates the embedded WebServer instance, configures connectors, and binds to the network port (e.g. 8080).
  3. This occurs after BeanFactoryPostProcessors have run, but before regular application singleton beans are eagerly instantiated.
  4. The DispatcherServlet is then registered and mapped to the embedded server before the context finishes refreshing.

🔴 Senior Level

Deep Dive: context.refresh() Phases

The core of SpringApplication.run() is AbstractApplicationContext.refresh(). In the context of @SpringBootApplication, the critical phases execute in this order:

  1. invokeBeanFactoryPostProcessors(beanFactory):
    • Spring invokes ConfigurationClassPostProcessor.
    • It parses @SpringBootApplication and executes @ComponentScan using the ASM bytecode reader to create BeanDefinitions for all application components.
    • It executes AutoConfigurationImportSelector, reading the .imports registry, evaluating @Conditional conditions, and registering auto-configurations.
  2. registerBeanPostProcessors(beanFactory):
    • Instantiates and registers all BeanPostProcessors (such as AutowiredAnnotationBeanPostProcessor and AnnotationAwareAspectJAutoProxyCreator).
  3. onRefresh():
    • Overridden in ServletWebServerApplicationContext to start the embedded web server (Tomcat, Jetty, or Undertow).
  4. finishBeanFactoryInitialization(beanFactory):
    • Calls beanFactory.preInstantiateSingletons(): instantiates, injects dependencies, and initializes all non-lazy singleton beans.
  5. finishRefresh():
    • Publishes ContextRefreshedEvent and starts any SmartLifecycle components.

The Disaster of Placing @SpringBootApplication in the Default Package

If a developer declares the main application class without a package declaration (package com.example... is omitted):

  • The class resides in Java’s Default Package (root).
  • Because @ComponentScan defaults to scanning the declaring package and all subpackages, Spring Boot attempts to scan the entire JVM classpath.
  • Every single .class file inside every dependency JAR in the project is scanned for annotations.
  • Consequences: Startup time balloons from 2 seconds to several minutes, massive Metaspace and heap consumption occurs, and conflicting classes in third-party libraries trigger fatal dependency resolution crashes.

Diagnostics via FailureAnalyzer

When startup fails (e.g., port 8080 is already bound, or an unresolvable dependency cycle exists), Spring Boot catches the exception and routes it through registered implementations of the FailureAnalyzer SPI:

============================
APPLICATION FAILED TO START
============================

Description:
Web server failed to start. Port 8080 was already in use.

Action:
Identify and stop the process that is listening on port 8080 or configure this application to listen on another port.

Instead of printing a raw 300-line stack trace, analyzers like PortInUseFailureAnalyzer and NoSuchBeanDefinitionFailureAnalyzer produce human-readable descriptions and concrete remediation actions.

Post-Startup Execution: CommandLineRunner vs ApplicationRunner

After the ApplicationContext is fully refreshed and the web server is listening, Spring Boot executes all beans implementing runner interfaces before entering the steady state:

Runner Interface Method Signature Argument Handling
CommandLineRunner run(String... args) Receives raw, unparsed string array: ["--port=9090", "debug"]
ApplicationRunner run(ApplicationArguments args) Provides parsed access: args.getOptionValues("port"), args.getNonOptionArgs()
@Component
public class DatabaseSeeder implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) {
        if (args.containsOption("seed-demo-data")) {
            System.out.println("Seeding demonstration records into database...");
        }
    }
}

4 Tricky Questions

1. What disastrous consequences occur if a developer places @SpringBootApplication in the Java default package (no package declaration)?

Answer: If @SpringBootApplication is placed in the default package, @ComponentScan without an explicit scanBasePackages defaults to scanning the entire root package of the JVM.

This causes Spring’s component scanner to traverse the entire classpath, inspecting every class inside every dependency JAR (Spring Framework, Hibernate, Jackson, JDBC drivers, etc.). This leads to:

  • Drastic startup degradation (startup time increases by orders of magnitude).
  • Metaspace and memory exhaustion from loading thousands of class bytecodes into ASM metadata readers.
  • Accidental component registration of internal classes within third-party JARs, triggering BeanDefinitionStoreException or dependency injection conflicts.

2. At what exact lifecycle phase is the embedded Tomcat/Jetty server instantiated and started during SpringApplication.run()?

Answer: The embedded web server is instantiated and started inside the context.refresh() phase, specifically during the onRefresh() hook method of ServletWebServerApplicationContext.

At this point, all BeanFactoryPostProcessors (including component scanning and auto-configuration loading) have completed, and BeanPostProcessors are registered, but application singleton beans have not yet been instantiated. Spring calls ServletWebServerFactory.getWebServer() to start the server connector, binding the network port before eager singletons are initialized.

3. What is the fundamental difference between CommandLineRunner and ApplicationRunner, and when do they execute?

Answer: Both runner interfaces execute strictly after context.refresh() completes and the embedded web server is started, but before SpringApplication.run() returns and the application enters steady state.

The difference lies in argument parsing:

  • CommandLineRunner: Passes raw CLI arguments as a plain Java string array (String... args).
  • ApplicationRunner: Wraps arguments in an ApplicationArguments object, providing pre-parsed access to option arguments (--server.port=9090 via getOptionValues()) and non-option arguments.

4. Why does Spring Boot introduce @SpringBootConfiguration instead of simply using Spring Framework’s standard @Configuration?

Answer: @SpringBootConfiguration is a meta-annotation that encapsulates @Configuration. Its primary technical role is to serve as an anchor marker for Spring Boot testing infrastructure.

When running integration slice tests (such as @SpringBootTest, @WebMvcTest, or @DataJpaTest), Spring’s test framework automatically searches upward through the package hierarchy from the test class until it finds a class annotated with @SpringBootConfiguration. This allows the test framework to locate the root application configuration without requiring developers to specify test configuration classes manually.


🎯 Interview Cheat Sheet

30-Second Elevator Pitch

@SpringBootApplication is the primary composite bootstrap annotation in Spring Boot, combining:

  1. @SpringBootConfiguration: Declares the class as a configuration source and serves as the configuration anchor for @SpringBootTest.
  2. @EnableAutoConfiguration: Triggers auto-configuration discovery via AutoConfigurationImportSelector.
  3. @ComponentScan: Scans the current package and all child subpackages for @Component, @Service, @Repository, and @Controller beans.

When SpringApplication.run() executes, it prepares the environment, creates the ApplicationContext, starts the embedded web server (Tomcat) in onRefresh(), eagerly instantiates all singletons, runs CommandLineRunner / ApplicationRunner beans, and publishes ApplicationReadyEvent.

Composition Breakdown Matrix

Constituent Annotation Core Purpose Default Scope
@SpringBootConfiguration Defines @Bean definitions and test root Declaring class
@EnableAutoConfiguration Loads classpath-based auto-configurations Global context
@ComponentScan Discovers application components Declaring package + subpackages

Red Flags (DO NOT Say)

  • ❌ “@SpringBootApplication is just a syntax shortcut for public static void main.” (It is a composite configuration, scanning, and auto-configuration meta-annotation).
  • ❌ “Tomcat starts before the Spring ApplicationContext is created.” (Tomcat is created and started inside context.refresh() during the onRefresh() phase).
  • ❌ “You can put the main application class in the default root package without issues.” (This forces @ComponentScan to scan all dependency JARs on the classpath, severely degrading startup).
  • ❌ “CommandLineRunner executes before beans are initialized.” (Runners execute strictly after the context is fully refreshed and all singletons exist).