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
| Annotation | Job |
|---|---|
| @SpringBootConfiguration | Marks the class as a configuration class (special @Configuration) |
| @EnableAutoConfiguration | Turns on auto-configuration |
| @ComponentScan | Scans 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
| Condition | Meaning |
|---|---|
| @ConditionalOnClass | Class is on the classpath (for example DataSource.class) |
| @ConditionalOnMissingClass | Class is NOT on the classpath |
| @ConditionalOnBean | A bean of this type already exists |
| @ConditionalOnMissingBean | No such bean exists. This is how Boot backs off when you define your own |
| @ConditionalOnProperty | A property has a given value |
| @ConditionalOnWebApplication | The app is a web application |
| @ConditionalOnResource | A 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.GreetingAutoConfigurationPut 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.DataSourceAutoConfigurationTip: 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 11Important: @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 configuredYour 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.