How Spring Boot Auto-Configuration Works — The Magic Explained
Spring Boot “just works” — you add a dependency and things appear in your application context. This article explains exactly how that happens, so you can debug it when it doesn’t, and extend it when you need to.
What Auto-Configuration Actually Does
When you add spring-boot-starter-data-jpa to your project, you don’t write a single line of config — yet Spring creates a DataSource, EntityManagerFactory, and JpaTransactionManager automatically. That’s auto-configuration.
Auto-configuration is a set of @Configuration classes that Spring Boot ships. Each one is guarded by conditions — it only activates if certain classes are on the classpath, certain beans are missing, certain properties are set, or certain files exist.
The net effect: your app gets sensible defaults, and you override only what you need.
The Entry Point: @SpringBootApplication
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
@SpringBootApplication is a composed annotation that includes:
@SpringBootConfiguration // marks this as a configuration class
@EnableAutoConfiguration // turns on auto-configuration
@ComponentScan // scans the package and sub-packages
@EnableAutoConfiguration is the key. It triggers the auto-configuration machinery.
How Spring Discovers Auto-Configuration Classes
Spring Boot 3+ uses a file called:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
(In Spring Boot 2 it was META-INF/spring.factories — same idea, different format.)
Every Spring Boot starter JAR ships this file listing its auto-configuration classes. When @EnableAutoConfiguration activates, Spring Boot reads all these files from every JAR on the classpath and collects the full list of candidate configurations.
You can see all of them:
# List every auto-configuration class loaded
./mvnw spring-boot:run \
-Dspring-boot.run.jvmArguments="-Ddebug"
Or look in the actuator:
GET /actuator/conditions
The response shows every auto-configuration and whether it matched or was skipped — and why.
The @Conditional System
Auto-configuration classes don’t blindly apply. Every one is wrapped in conditions. Spring Boot ships a rich set:
| Annotation | Condition |
|---|---|
@ConditionalOnClass | A class must be on the classpath |
@ConditionalOnMissingClass | A class must NOT be on the classpath |
@ConditionalOnBean | A bean of a certain type must exist |
@ConditionalOnMissingBean | A bean of a certain type must NOT exist |
@ConditionalOnProperty | A property must be set (or have a specific value) |
@ConditionalOnResource | A file must exist on the classpath |
@ConditionalOnWebApplication | App must be a web application |
@ConditionalOnExpression | A SpEL expression must evaluate to true |
A Real Example: DataSourceAutoConfiguration
Here’s a simplified version of how Spring Boot auto-configures a DataSource:
@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceCheckpointRestoreConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@ConditionalOnSingleCandidate(DataSourceProperties.class)
protected static class PooledDataSourceConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DataSourceProperties properties) {
// creates HikariCP pool with properties from application.properties
}
}
}
What this says:
- Only activate if
DataSourceclass is on the classpath (@ConditionalOnClass) - If there’s already a
DataSourcebean — back off (@ConditionalOnMissingBean) - Read config from
spring.datasource.*properties - Create the
DataSourcebean
This is the fundamental pattern: auto-configuration backs off whenever you provide your own bean.
The Request Flow Through Auto-Configuration
Application starts
│
▼
@SpringBootApplication found
│
▼
@EnableAutoConfiguration activates
│
▼
Spring reads all AutoConfiguration.imports files from classpath JARs
│
▼
Collects ~150 candidate auto-configuration classes
│
▼
For each candidate:
- Evaluate @Conditional annotations
- If all conditions pass → apply the @Configuration
- If any condition fails → skip (logged in /actuator/conditions)
│
▼
Beans from active configurations are added to ApplicationContext
│
▼
Your @Component/@Service/@Repository beans are registered
│
▼
Application ready
Seeing It in Action
Add the Actuator and hit the conditions endpoint:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.properties
management.endpoints.web.exposure.include=conditions,beans
# See all conditions decisions
curl http://localhost:8080/actuator/conditions | jq .
# See all beans in the context
curl http://localhost:8080/actuator/beans | jq .
The conditions report has two sections:
positiveMatches— configurations that were applied and whynegativeMatches— configurations that were skipped and which condition failed
Example: Why DataSource was auto-configured
{
"positiveMatches": {
"DataSourceAutoConfiguration": [
{
"condition": "OnClassCondition",
"message": "@ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'"
}
]
}
}
Example: Why WebMvcAutoConfiguration was skipped (in a non-web app)
{
"negativeMatches": {
"WebMvcAutoConfiguration": {
"notMatched": [
{
"condition": "OnWebApplicationCondition",
"message": "@ConditionalOnWebApplication did not find reactive or servlet-based web application"
}
]
}
}
}
Overriding Auto-Configuration
There are three ways to override:
1. Define your own bean
The most common. Auto-configuration uses @ConditionalOnMissingBean, so your bean wins:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
// Your custom DataSource — auto-config backs off
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:postgresql://localhost:5432/orders");
ds.setUsername("app");
ds.setPassword("secret");
ds.setMaximumPoolSize(20);
return ds;
}
}
2. Set properties
Most auto-configurations are driven by properties. You don’t need a custom bean — just configure:
# application.yml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/orders
username: app
password: secret
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
3. Exclude an auto-configuration entirely
If you want to disable an auto-configuration class:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class OrderServiceApplication { ... }
Or via properties:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Writing Your Own Auto-Configuration
You can write custom auto-configuration for your own libraries or shared components.
Step 1: Write a @AutoConfiguration class:
@AutoConfiguration
@ConditionalOnClass(OrderRepository.class)
@ConditionalOnMissingBean(OrderService.class)
@EnableConfigurationProperties(OrderServiceProperties.class)
public class OrderServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OrderService orderService(OrderRepository repository,
OrderServiceProperties props) {
return new OrderService(repository, props.getMaxItemsPerOrder());
}
}
Step 2: Create the properties class:
@ConfigurationProperties(prefix = "order")
public class OrderServiceProperties {
private int maxItemsPerOrder = 50;
public int getMaxItemsPerOrder() { return maxItemsPerOrder; }
public void setMaxItemsPerOrder(int n) { this.maxItemsPerOrder = n; }
}
Step 3: Register it in src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.devopsmonk.order.autoconfigure.OrderServiceAutoConfiguration
Step 4: Any application that includes your JAR gets OrderService auto-wired — unless they define their own.
The @ConfigurationProperties Pattern
Auto-configurations bind external properties to typed beans using @ConfigurationProperties. You should use this for your own configuration too — it’s far better than injecting individual @Value fields.
@ConfigurationProperties(prefix = "app.order")
@Validated
public class AppOrderProperties {
@NotNull
private String defaultCurrency = "USD";
@Min(1) @Max(1000)
private int maxItemsPerOrder = 50;
private Duration processingTimeout = Duration.ofSeconds(30);
// getters and setters
}
Enable it in your main configuration:
@SpringBootApplication
@EnableConfigurationProperties(AppOrderProperties.class)
public class OrderServiceApplication { ... }
Or annotate the properties class with @Component directly.
Now configure it in application.yml:
app:
order:
default-currency: EUR
max-items-per-order: 100
processing-timeout: 60s
Spring Boot will validate these at startup and give a clear error if they’re wrong.
Common Debugging Scenarios
Problem: My DataSource isn’t being created.
# Enable debug logging
logging.level.org.springframework.boot.autoconfigure=DEBUG
Check the conditions report. Look for DataSourceAutoConfiguration in negativeMatches. The reason will tell you exactly which condition failed.
Problem: I defined a DataSource bean but there are now two.
Check if your bean is defined and the auto-configuration is also active. Auto-config should back off via @ConditionalOnMissingBean. If it didn’t, you may have defined the bean conditionally yourself, and auto-config evaluated before your bean was registered.
Problem: I want auto-configuration for a profile-specific bean.
Use @Profile alongside @Bean:
@Bean
@Profile("prod")
public DataSource prodDataSource() { ... }
Auto-configuration’s @ConditionalOnMissingBean is context-aware — it will see your profile-activated bean only if that profile is active.
What You’ve Learned
@SpringBootApplicationincludes@EnableAutoConfiguration- Spring Boot discovers auto-configuration classes via
AutoConfiguration.importsin each JAR - Every auto-configuration is guarded by
@Conditionalannotations — it backs off if conditions aren’t met - The most important condition is
@ConditionalOnMissingBean— define your own bean and auto-config steps aside - Use
/actuator/conditionsto see exactly why each configuration was applied or skipped - Write your own auto-configuration using the same pattern
Next: Article 5 — Application Configuration: Properties, YAML, and Profiles — how to configure your app for different environments without changing code.