Spring Boot Auto-Configuration: In‑Depth Source Code Walkthrough

This article explores the complete execution flow of Spring Boot’s auto‑configuration, detailing the role of @EnableAutoConfiguration, the AutoConfigurationImportSelector hierarchy, how candidate configurations are loaded from spring.factories, filtered by conditions, and how to create custom starters, with interview‑style Q&A.

Coder Trainee
Coder Trainee
Coder Trainee
Spring Boot Auto-Configuration: In‑Depth Source Code Walkthrough

1. Auto‑Configuration Entry Recap

Previously we covered the composition of @SpringBootApplication and identified @EnableAutoConfiguration as the entry point for auto‑configuration.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

Core: The @Import(AutoConfigurationImportSelector.class) imports AutoConfigurationImportSelector.

2. AutoConfigurationImportSelector Source Analysis

2.1 Class inheritance hierarchy

ImportSelector (interface)
    │
    └── DeferredImportSelector (interface, delayed import)
            │
            └── AutoConfigurationImportSelector
DeferredImportSelector

executes after all @Configuration classes have been parsed, ensuring user‑defined beans can override auto‑configured ones.

2.2 selectImports method

// AutoConfigurationImportSelector.java
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 1. Check if auto‑configuration is enabled (default true)
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 2. Load auto‑configuration metadata
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    // 3. Return the array of configuration class names
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

2.3 getAutoConfigurationEntry method

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 1. Check if auto‑configuration is enabled
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 2. Retrieve annotation attributes (exclude, excludeName)
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 3. Load all candidate configuration classes from spring.factories
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 4. Remove duplicates
    configurations = removeDuplicates(configurations);
    // 5. Process exclude items
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    configurations.removeAll(exclusions);
    // 6. Conditional filtering (@Conditional annotations)
    configurations = filter(configurations, autoConfigurationMetadata);
    // 7. Trigger auto‑configuration import events
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

2.4 Loading candidates from spring.factories

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
    // Key: load from spring.factories
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
            getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories");
    return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    // Returns EnableAutoConfiguration.class
    return EnableAutoConfiguration.class;
}

2.5 Conditional filtering

private List<String> filter(List<String> configurations,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    // Candidate list
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean[] skip = new boolean[candidates.length];
    // Iterate each candidate and check conditions
    for (int i = 0; i < candidates.length; i++) {
        String candidate = candidates[i];
        // Use metadata to see if @Conditional is present
        if (autoConfigurationMetadata.wasProcessed(candidate)) {
            // Skip if conditions are not satisfied
            skip[i] = !autoConfigurationMetadata.getConditions(candidate).isEmpty();
        }
    }
    // Collect non‑skipped configurations
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    return result;
}

3. How Conditional Annotations Work

3.1 Core interface

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

3.2 Example: @ConditionalOnClass

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    Class<?>[] value() default {};
    String[] name() default {};
}

3.3 OnClassCondition core logic

public class OnClassCondition extends SpringBootCondition {
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        // Get annotation attributes
        MultiValueMap<String, Object> attributes =
                metadata.getAllAnnotationAttributes(ConditionalOnClass.class.getName(), true);
        // Classes that must be present
        List<String> onClasses = getClasses(attributes, "value");
        // Classes that must be missing
        List<String> onMissingClasses = getClasses(attributes, "name");
        // Check classpath
        ClassLoader classLoader = context.getClassLoader();
        // (Implementation details omitted for brevity)
        return new ConditionOutcome(match, message);
    }
}

3.4 Execution flow

Auto‑configuration class
    ▼
Parse @Conditional annotations
    ▼
Instantiate corresponding Condition class (e.g., OnClassCondition)
    ▼
Call matches()
    ├── Condition satisfied → load configuration class
    └── Condition not satisfied → skip configuration class

4. Spring Boot 2.7 New Feature: Auto‑Configuration File Location Change

Before 2.7 the list of auto‑configuration classes lived in META-INF/spring.factories. Starting with 2.7 the format is gradually deprecated in favour of

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

, where each line contains a single fully‑qualified configuration class.

# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

This one‑line‑per‑class format is more concise.

5. Custom Starter Practical Guide

5.1 Create auto‑configuration class

// Configuration properties class
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
    private String prefix = "Hello";
    private String suffix = "!";
    // getters/setters omitted
}

// Service class
public class HelloService {
    private String prefix;
    private String suffix;
    public HelloService(String prefix, String suffix) {
        this.prefix = prefix;
        this.suffix = suffix;
    }
    public String sayHello(String name) {
        return prefix + " " + name + suffix;
    }
}

// Auto‑configuration class
@Configuration
@ConditionalOnClass(HelloService.class)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public HelloService helloService(HelloProperties properties) {
        return new HelloService(properties.getPrefix(), properties.getSuffix());
    }
}

5.2 Register the starter

For Spring Boot 2.7+ create

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

: com.example.hello.HelloAutoConfiguration For earlier versions add the class name to spring.factories under the EnableAutoConfiguration key.

5.3 Use the starter

In application.yml:

hello:
  prefix: "你好"
  suffix: "~"

Inject and call:

@Autowired
private HelloService helloService;

helloService.sayHello("老J"); // → 你好 老J~

6. Common Interview Questions

Q1: What is the execution order of auto‑configuration?

Answer: AutoConfigurationImportSelector implements DeferredImportSelector, so it runs after all regular @Configuration classes are processed. Ordering can be further controlled with @AutoConfigureOrder, @AutoConfigureAfter and @AutoConfigureBefore.

Q2: How do conditional annotations work?

Each @Conditional links to a Condition implementation. Spring Boot invokes the matches() method; for example @ConditionalOnClass checks classpath presence, while @ConditionalOnMissingBean checks the bean registry.

Q3: How to create a custom starter?

Write an auto‑configuration class annotated with @ConfigurationProperties to define configurable properties. Use @Conditional (e.g., @ConditionalOnClass ) to control when the configuration is applied. Register the class in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (or spring.factories for pre‑2.7). Add the starter dependency to the consuming project.

7. One‑Page Summary Diagram

@SpringBootApplication
    │
    └── @EnableAutoConfiguration
            │
            └── @Import(AutoConfigurationImportSelector)
                    ▼
                selectImports()
                    ▼
                getAutoConfigurationEntry()
        ┌───────────────┼───────────────┐
        ▼               ▼               ▼
   Load spring.factories   Process exclude   @Conditional filtering
        │               │               │
        └───────────────┴───────────────┘
                    ▼
            Return eligible configuration classes
                    ▼
            Spring container registers them
                    ▼
            @Bean definitions become active

8. Next Issue Preview

Spring Boot source analysis (Part 4): Deep dive into IoC container initialization, covering the 12 steps of refresh(), bean creation flow, three‑level cache for circular dependencies, and essential interview topics.

9. Interaction

Feel free to ask questions about auto‑configuration, conditional annotations, custom starters, or ordering issues in the comments.

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.

JavaBackend Developmentspring-bootAuto-configurationCustom StarterConditional Annotations
Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

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.