Home / Spring Boot / Getting Started / Auto-Configuration Internals

Auto-Configuration Internals

Auto-configuration is the heart of Spring Boot. It reads your classpath and properties and creates beans for you, but always backs off when you define your own beans. This page shows exactly how it works and how to write your own.

@SpringBootApplication is three annotations

AnnotationJob
@SpringBootConfigurationMarks the class as a configuration class (special @Configuration)
@EnableAutoConfigurationTurns on auto-configuration
@ComponentScanScans the current package and sub-packages for components

How Spring Boot finds auto-configurations

spring-boot-autoconfigure.jar
  └─ META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
        org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
        org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
        org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
        ... (hundreds of classes)

At startup Spring reads this file and checks the @Conditional rules of each class.
If all conditions are true, its @Bean methods run. If not, the class is skipped.

Conditions

ConditionMeaning
@ConditionalOnClassClass is on the classpath (for example DataSource.class)
@ConditionalOnMissingClassClass is NOT on the classpath
@ConditionalOnBeanA bean of this type already exists
@ConditionalOnMissingBeanNo such bean exists. This is how Boot backs off when you define your own
@ConditionalOnPropertyA property has a given value
@ConditionalOnWebApplicationThe app is a web application
@ConditionalOnResourceA file exists on the classpath

Write your own auto-configuration

// 1. the service we want to configure
public class GreetingService {
    private final String greeting;
    public GreetingService(String greeting) { this.greeting = greeting; }
    public String greet(String name) { return greeting + ", " + name + "!"; }
}

// 2. properties
@ConfigurationProperties(prefix = "greeting")
public class GreetingProperties {
    private String message = "Hello";
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
}

// 3. the auto-configuration class
@AutoConfiguration
@ConditionalOnClass(GreetingService.class)
@EnableConfigurationProperties(GreetingProperties.class)
public class GreetingAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean            // back off if the user defines a GreetingService
    public GreetingService greetingService(GreetingProperties props) {
        return new GreetingService(props.getMessage());
    }
}
# file: src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.greeting.GreetingAutoConfiguration

Put these classes in a separate library (a starter). Any application that adds this library gets a GreetingService bean automatically, and can change the message with greeting.message=Namaste in its properties.

See what was configured (debug report)

java -jar app.jar --debug
# or in application.properties:  debug=true
#
# Positive matches:   DataSourceAutoConfiguration matched: @ConditionalOnClass found ...
# Negative matches:   MongoAutoConfiguration did not match: @ConditionalOnClass did not find ...

Exclude an auto-configuration

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DemoApplication { }
// or: spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

Tip: Back-off rule: if you define your own bean of the same type (for example your own ObjectMapper or DataSource), the matching auto-configuration is skipped because of @ConditionalOnMissingBean. You never have to fight auto-configuration.

Internal workflow: from the imports file to registered beans

@EnableAutoConfiguration
   └─ @Import(AutoConfigurationImportSelector)      a DeferredImportSelector (runs AFTER all your own @Configuration classes)
        1. getCandidateConfigurations(): read META-INF/spring/...AutoConfiguration.imports from EVERY jar on the classpath
        2. removeDuplicates()
        3. getExclusions(): @SpringBootApplication(exclude=...), spring.autoconfigure.exclude
        4. filter(): AutoConfigurationImportFilter (OnClassCondition, OnBeanCondition, OnWebApplicationCondition)
              a FAST pre-check using the metadata file spring-autoconfigure-metadata.properties
              (drops hundreds of classes without loading them: no class loading = fast startup)
        5. fireAutoConfigurationImportEvents()  ─► the ConditionEvaluationReport is filled
        6. sort by @AutoConfiguration(before=, after=) and @AutoConfigureOrder
   ▼
ConfigurationClassPostProcessor parses each remaining class like a normal @Configuration:
        class-level conditions (@ConditionalOnClass ...)  ─► then each @Bean method's own conditions (@ConditionalOnMissingBean ...)
   ▼
Bean definitions for the surviving @Bean methods are registered ─► beans are created later in refresh() step 11

Important: @ConditionalOnBean and @ConditionalOnMissingBean depend on registration ORDER. Auto-configurations run after your own configuration, so your beans are already registered and win. That is why the 'back off' principle works and why these conditions are only reliable in auto-configuration classes, not in normal @Configuration classes.

Ordering auto-configurations

@AutoConfiguration(after = DataSourceAutoConfiguration.class, before = HibernateJpaAutoConfiguration.class)
@ConditionalOnClass(DataSource.class)
public class AuditAutoConfiguration {
    @Bean
    @ConditionalOnBean(DataSource.class)           // safe here: DataSourceAutoConfiguration ran before
    AuditRepository auditRepository(DataSource dataSource) { return new AuditRepository(dataSource); }
}

@ConditionalOnProperty in detail

@Bean
@ConditionalOnProperty(prefix = "app.cache", name = "enabled", havingValue = "true", matchIfMissing = false)
CacheWarmer cacheWarmer() { return new CacheWarmer(); }
// app.cache.enabled=true  -> bean exists
// property missing        -> bean does NOT exist (matchIfMissing=false). With true it would exist by default.

@Bean
@ConditionalOnProperty(name = "app.mode", havingValue = "fast")
Engine fastEngine() { return new FastEngine(); }
@Bean
@ConditionalOnProperty(name = "app.mode", havingValue = "safe", matchIfMissing = true)
Engine safeEngine() { return new SafeEngine(); }        // the default engine when nothing is configured

Your own Condition

public class OnLinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name", "").toLowerCase().contains("linux");
    }
}

@Bean
@Conditional(OnLinuxCondition.class)
NativeIo linuxIo() { return new EpollIo(); }

// Or extend SpringBootCondition to give a readable reason in the --debug report
public class OnFeatureFlagCondition extends SpringBootCondition {
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        boolean on = context.getEnvironment().getProperty("feature.x", Boolean.class, false);
        return on ? ConditionOutcome.match("feature.x is on") : ConditionOutcome.noMatch("feature.x is off");
    }
}

A real auto-configuration, simplified (how DataSource gets created)

@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })          // a JDBC API is on the classpath
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)                     // binds spring.datasource.*
public class DataSourceAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)                              // url or embedded DB + a pool library present
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })        // BACK OFF if the user defined a DataSource
    @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class })
    protected static class PooledDataSourceConfiguration { }                    // Hikari first, if its class is present
}

Read real auto-configuration classes in your IDE (Ctrl+click WebMvcAutoConfiguration). The same handful of annotations repeat in all of them, and the source explains every default better than any tutorial.

Tools to inspect and test auto-configuration

java -jar app.jar --debug                                   # conditions report at startup
curl localhost:8080/actuator/conditions | jq '.contexts[].positiveMatches | keys'   # needs actuator
# Unit test for an auto-configuration:
#   new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(GreetingAutoConfiguration.class))
#        .withPropertyValues("greeting.message=Hi").run(ctx -> assertThat(ctx).hasSingleBean(GreetingService.class));
class GreetingAutoConfigurationTest {
    private final ApplicationContextRunner runner = new ApplicationContextRunner()
            .withConfiguration(AutoConfigurations.of(GreetingAutoConfiguration.class));

    @Test
    void createsDefaultBean() {
        runner.run(ctx -> assertThat(ctx).hasSingleBean(GreetingService.class));
    }

    @Test
    void backsOffWhenUserDefinesOwnBean() {
        runner.withBean("myGreeting", GreetingService.class, () -> new GreetingService("Own"))
              .run(ctx -> assertThat(ctx.getBean(GreetingService.class).greet("x")).startsWith("Own"));
    }
}

Common mistake: Putting the auto-configuration class inside a component-scanned package and also in the imports file. Auto-configuration classes should live outside the scanned packages and be registered only in the imports file.

Interview questions

Where is the list of auto-configuration classes?

In META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports inside spring-boot-autoconfigure and in other starters. Older versions used spring.factories.

How do I know why a bean was or was not created?

Start the app with --debug or debug=true, or use the Actuator conditions endpoint.

How can I disable one auto-configuration?

Use exclude in @SpringBootApplication or the property spring.autoconfigure.exclude.

Why is @ConditionalOnBean unreliable in my own @Configuration?

Bean definitions are registered in configuration parsing order. A condition evaluated before the referenced bean is registered gives a wrong answer. Use these conditions in auto-configuration classes, which are ordered after user configuration.

How does Boot avoid loading hundreds of classes at startup?

A metadata file lets the import filter check @ConditionalOnClass cheaply without loading the class, so most auto-configurations are dropped before they are parsed.

How do I test my own auto-configuration?

With ApplicationContextRunner: it starts a tiny context per test, so you can assert which beans exist with which properties and user beans.