Understanding the SpringApplication Startup Process in Spring Boot

This article explains how Spring Boot launches an application by creating an AnnotationConfigApplicationContext, registering annotation processors, loading the primary source class, and invoking ConfigurationClassPostProcessor during the refresh phase, with detailed code examples illustrating each step.

Architecture Digest
Architecture Digest
Architecture Digest
Understanding the SpringApplication Startup Process in Spring Boot

From SpringApplication Start

Typically a Spring Boot application is started by defining a class with a main method and calling SpringApplication.run:

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

receives two arguments: the primarySource (the class passed in) and the command‑line arguments. Internally it creates an ApplicationContext by invoking the protected createApplicationContext method.

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        } catch (ClassNotFoundException ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

In a non‑web environment the created context is an AnnotationConfigApplicationContext. Its default constructor creates two helper objects: AnnotatedBeanDefinitionReader – used for manual bean registration. ClassPathBeanDefinitionScanner – scans for @Component, @Repository, @Service, etc.

Both helpers register a set of annotation processors via AnnotationConfigUtils.registerAnnotationConfigProcessors:

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
        BeanDefinitionRegistry registry, @Nullable Object source) {
    // ...
    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
    // ...
    return beanDefs;
}

After the AnnotationConfigApplicationContext is fully constructed, SpringApplication loads the primarySource (e.g., AutoConfigApplication) into the context, resulting in an additional BeanDefinition for that class.

Summary of What SpringApplication Does

Creates an AnnotationConfigApplicationContext.

Registers annotation‑processing post‑processors such as ConfigurationClassPostProcessor.

Loads the primarySource (the class passed to run) into the context.

The most important point is that the context now contains both the user’s primary source bean and the configuration‑class post‑processor.

When Is @Configuration Processed?

After the primary source and post‑processors are in place, the ConfigurationClassPostProcessor (a BeanDefinitionRegistryPostProcessor) is invoked during the refresh phase of the ApplicationContext:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // ...
        invokeBeanFactoryPostProcessors(beanFactory);
        // ...
    }
}

public static void invokeBeanFactoryPostProcessors(
        ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
    // Find all BeanDefinitionRegistryPostProcessor beans
    String[] postProcessorNames = beanFactory.getBeanNamesForType(
            BeanDefinitionRegistryPostProcessor.class, true, false);
    for (String ppName : postProcessorNames) {
        // ... collect and sort ...
    }
    // Execute them
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
    // ...
}

private static void invokeBeanDefinitionRegistryPostProcessors(
        Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
}

The postProcessBeanDefinitionRegistry method of ConfigurationClassPostProcessor parses all @Configuration candidates, sorts them, and loads the resulting bean definitions:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    String[] candidateNames = registry.getBeanDefinitionNames();
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
            // already processed
        } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }
    if (configCandidates.isEmpty()) {
        return;
    }
    // Sort candidates
    configCandidates.sort((bd1, bd2) -> Integer.compare(
            ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()),
            ConfigurationClassUtils.getOrder(bd2.getBeanDefinition())));
    // Parse and load
    ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    // ... parsing loop omitted for brevity ...
    this.reader.loadBeanDefinitions(configClasses);
}

Although the example class AutoConfigApplication does not declare @Configuration directly, it is treated as a configuration class because @SpringBootApplication is meta‑annotated with @SpringBootConfiguration, which itself includes @Configuration.

Thus, the startup sequence ties together the creation of the application context, registration of essential post‑processors, and the parsing of configuration classes to fully initialise a Spring Boot application.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaConfigurationSpringBootapplicationcontextSpringApplication
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.