Spring Boot Auto-Configuration — How It Works and How to Override It
by Eric Hanson, Backend Developer at Clean Systems Consulting
What auto-configuration actually does
Auto-configuration is the mechanism that makes spring-boot-starter-data-jpa wire up a DataSource, EntityManagerFactory, and transaction manager without any explicit bean definitions. Add the dependency, provide the connection properties, and the infrastructure appears.
The mechanism: Spring Boot reads a list of auto-configuration classes from every JAR on the classpath. Each class carries @Conditional annotations that determine whether it applies. The conditions check for the presence of classes, existing beans, property values, and resource files. Only conditions that pass cause the auto-configuration to apply.
The list of auto-configuration classes is in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Spring Boot 2.7+, replacing spring.factories). Every Spring Boot starter ships this file — it's what makes starters self-configuring.
The @Conditional mechanism
Every auto-configuration class is annotated with one or more @Conditional annotations. The built-in conditions cover the common cases:
@ConditionalOnClass — applies only if a class is on the classpath:
@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
public class DataSourceAutoConfiguration {
// Only applies if DataSource and EmbeddedDatabaseType are on the classpath
}
Adding spring-boot-starter-data-jpa to the classpath satisfies this condition. Without the starter, the condition fails and no database auto-configuration applies — cleanly.
@ConditionalOnMissingBean — applies only if a bean of the specified type doesn't already exist:
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
This is the override pattern: define your own DataSource bean and this auto-configured bean doesn't apply. The condition checks the bean type (DataSource), not the bean name — any DataSource bean, regardless of name, satisfies the condition.
@ConditionalOnProperty — applies only if a property has a specific value:
@ConditionalOnProperty(
prefix = "spring.datasource",
name = "url"
)
public DataSource dataSource(...) { ... }
@ConditionalOnWebApplication — applies only in a web application context.
@ConditionalOnMissingClass — the inverse of @ConditionalOnClass.
@ConditionalOnSingleCandidate — applies only if exactly one bean of the type exists (used for auto-configuration that wraps a uniquely-identified infrastructure bean).
Override pattern 1: define your own bean
The primary override mechanism is defining a @Bean of the same type. Auto-configuration checks for your bean first and backs off:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(System.getenv("DATABASE_URL"));
config.setMaximumPoolSize(20);
config.setConnectionTimeout(30_000);
config.setLeakDetectionThreshold(60_000);
return new HikariDataSource(config);
}
}
Spring Boot's DataSourceAutoConfiguration sees your DataSource bean, the @ConditionalOnMissingBean(DataSource.class) condition fails, and the auto-configured bean is not created. Your configuration applies.
The subtlety: @ConditionalOnMissingBean checks the application context for the bean type at the point the condition is evaluated. User-defined beans are processed before auto-configuration classes — this is why defining your own bean reliably suppresses the auto-configured one.
Override pattern 2: properties
Most auto-configuration behavior is controlled by @ConfigurationProperties classes. Overriding a property changes the behavior without defining a new bean:
# Override auto-configured connection pool settings
spring:
datasource:
hikari:
maximum-pool-size: 20
connection-timeout: 30000
leak-detection-threshold: 60000
jpa:
hibernate:
ddl-auto: validate
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
jdbc:
batch_size: 50
The auto-configuration still applies — but its behavior is shaped by the properties rather than its defaults. This is the right choice when the auto-configured infrastructure is correct but needs tuning.
Override pattern 3: exclude specific auto-configuration
When you want to prevent auto-configuration from applying entirely — not override a specific bean, but disable the entire auto-configuration class:
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class
})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Or via properties (useful when the auto-configuration class is in a dependency you don't control):
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
Exclusion is appropriate when the auto-configuration conflicts with a manual setup and @ConditionalOnMissingBean overrides are insufficient. A common case: an application that uses multiple DataSource beans for different databases — Spring Boot's single DataSource auto-configuration doesn't fit, so exclude it and wire the data sources manually.
Auto-configuration ordering
Auto-configuration classes are not applied in arbitrary order. The @AutoConfiguration annotation (and @AutoConfigureBefore, @AutoConfigureAfter) controls ordering between auto-configuration classes:
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
public class HibernateJpaAutoConfiguration {
// Applies after DataSource is available — can safely reference DataSource
}
User-defined beans and @Configuration classes are processed before auto-configuration classes. This is the ordering guarantee that makes @ConditionalOnMissingBean reliable — user beans exist when auto-configuration conditions are evaluated.
The ordering within user-defined @Configuration classes: Spring processes them in the order they're registered, which is influenced by component scanning order (not alphabetical, not predictable). If two @Configuration classes define beans where order matters, use @DependsOn or restructure to eliminate the order dependency.
Writing your own auto-configuration
Auto-configuration isn't just for framework code — it's the right mechanism for wiring shared infrastructure in libraries used across multiple Spring Boot applications:
// In your shared library
@AutoConfiguration
@ConditionalOnClass(AuditService.class)
@ConditionalOnMissingBean(AuditService.class)
@EnableConfigurationProperties(AuditProperties.class)
public class AuditAutoConfiguration {
@Bean
public AuditService auditService(AuditProperties properties) {
return new DefaultAuditService(properties.getStorageBackend());
}
}
Register it in your library's META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.example.audit.AuditAutoConfiguration
Applications that include your library on the classpath get AuditService automatically. Applications that define their own AuditService bean get their version instead. The library's behavior is replaceable without modification.
The conditions evaluation report — the diagnostic tool
When a bean isn't being created and you don't know why, the conditions evaluation report shows exactly which conditions passed and which failed:
# application.properties
debug=true
# or
logging.level.org.springframework.boot.autoconfigure=DEBUG
This produces output like:
CONDITIONS EVALUATION REPORT
Positive matches:
-----------------
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)
Negative matches:
-----------------
MongoAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'com.mongodb.MongoClient' (OnClassCondition)
Exclusions:
-----------
None
Unconditional classes:
----------------------
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
"Positive matches" shows what applied. "Negative matches" shows what didn't and why. "Exclusions" shows what was explicitly excluded.
The three diagnostic questions the report answers:
- Why isn't my bean being created? Look in negative matches — find your auto-configuration class and read the failed condition.
- Why are there two beans of the same type? Look in positive matches — both your bean definition and the auto-configured bean may be applying.
- What auto-configuration is the classpath triggering? Scan positive matches for unexpected auto-configuration.
The override decision
The three patterns serve different needs:
Define your own bean (@ConditionalOnMissingBean) when you need different behavior or a different implementation — a custom DataSource with specific pool settings, a custom ObjectMapper with different serialization configuration.
Override properties when the auto-configured implementation is correct but needs tuning — Hikari pool size, JPA batch size, cache TTLs.
Exclude the auto-configuration when it conflicts with a manual setup or when its presence would cause errors — multiple data sources, custom transaction management, third-party infrastructure that conflicts with Spring Boot's defaults.
The conditions evaluation report is the diagnostic tool for all three — it shows what applied, what didn't, and why. With it, auto-configuration is a transparent system with inspectable behavior, not a black box.