How Spring Boot Auto-Configuration Actually Works (Behind the Magic)
“Spring Boot is magic” is something you hear a lot. Add spring-boot-starter-data-jpa and suddenly you have a working DataSource, a JpaTransactionManager, and a LocalContainerEntityManagerFactoryBean — without writing a single @Bean method. Understanding how this actually works turns the magic into a tool you can control, debug, and extend.
The Entry Point: @EnableAutoConfiguration
@SpringBootApplication is a shorthand for three annotations:
@Configuration
@EnableAutoConfiguration // this is the one that matters here
@ComponentScan
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@EnableAutoConfiguration tells Spring to scan for auto-configuration classes and activate the ones that match the current application context.
How Spring Finds Auto-Configurations
Auto-configuration classes are registered in a file inside each JAR:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
Spring Boot reads this file from every JAR on the classpath at startup. Each line is a fully-qualified auto-configuration class name:
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
# ... hundreds more
In Spring Boot 3.x, all of these were in a single spring-boot-autoconfigure JAR. In Spring Boot 4, this was split into 70+ individual modules — each starter pulls in only the modules it needs, reducing startup processing time significantly.
flowchart TD
App[SpringApplication.run] --> Scan[Scan AutoConfiguration.imports\nin all JARs on classpath]
Scan --> List[Load list of candidate\nauto-configuration classes]
List --> Filter[Evaluate @Conditional\nannotations on each class]
Filter --> Active[Activate matching configurations]
Filter --> Skip[Skip non-matching ones]
Active --> Beans[Register beans into\nApplication Context]
Conditional Annotations: The Core Mechanism
Every auto-configuration class is guarded by one or more @Conditional annotations. These evaluate at startup and determine whether the configuration class is activated.
@ConditionalOnClass
Activates only if a specific class is present on the classpath:
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // only if DataSource is on the classpath
public class DataSourceAutoConfiguration {
// This entire class is skipped if you have no JDBC starter
}
This is how spring-boot-starter-data-jpa triggers JPA auto-configuration: the JPA JAR puts EntityManager on the classpath, which satisfies @ConditionalOnClass(EntityManager.class).
@ConditionalOnMissingBean
Activates only if you have NOT defined a bean of that type yourself:
@AutoConfiguration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean(DataSource.class) // backs off if you define your own
public DataSource dataSource(DataSourceProperties properties) {
return DataSourceBuilder.create()
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword())
.build();
}
}
This is the non-invasive principle: your explicit @Bean definitions always take precedence over auto-configuration. Define your own DataSource bean and the auto-configured one never gets created.
@ConditionalOnProperty
Activates only if a property is set to a specific value:
@Bean
@ConditionalOnProperty(
name = "spring.cache.type",
havingValue = "redis",
matchIfMissing = false
)
public CacheManager redisCacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.create(factory);
}
matchIfMissing = true means: activate even if the property is not set (i.e., use this as the default). matchIfMissing = false means: only activate when the property is explicitly set.
@ConditionalOnBean
The inverse of @ConditionalOnMissingBean — activates only if another bean already exists:
@Bean
@ConditionalOnBean(DataSource.class) // only create if there is a DataSource
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
Other Conditionals
| Annotation | Condition |
|---|---|
@ConditionalOnWebApplication | Running in a web context |
@ConditionalOnNotWebApplication | Not running in a web context |
@ConditionalOnResource | A specific file resource exists |
@ConditionalOnExpression | A SpEL expression evaluates to true |
@ConditionalOnSingleCandidate | Exactly one candidate bean exists |
@ConditionalOnJava | A specific Java version range |
@ConditionalOnCloudPlatform | Running on a specific cloud (Kubernetes, Heroku, etc.) |
Debugging: The Conditions Evaluation Report
When auto-configuration behaves unexpectedly — a bean is not being created, or the wrong one is — Spring Boot has a built-in diagnostic tool.
Start your application with --debug:
java -jar app.jar --debug
This prints a full CONDITIONS EVALUATION REPORT that shows every auto-configuration class, whether it was activated or not, and exactly why:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches: (configurations that DID activate)
-----------------
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required class 'javax.sql.DataSource' (OnClassCondition)
HibernateJpaAutoConfiguration matched:
- @ConditionalOnClass found required classes 'javax.persistence.EntityManager',
'org.hibernate.engine.spi.SessionImplementor' (OnClassCondition)
- @ConditionalOnBean (types: javax.sql.DataSource) found bean 'dataSource' (OnBeanCondition)
Negative matches: (configurations that DID NOT activate, and why)
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class
'javax.jms.ConnectionFactory' (OnClassCondition)
MongoAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class
'com.mongodb.client.MongoClient' (OnClassCondition)
This tells you exactly which condition caused each decision. If a bean you expect is missing, check the negative matches section first.
In the IDE: Set the logging.level.org.springframework.boot.autoconfigure to DEBUG in application.properties for the same output during development.
Excluding Auto-Configurations
To disable a specific auto-configuration:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication { }
Or via properties (useful for environment-specific exclusions):
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
Common reasons to exclude:
- Testing without a database (exclude
DataSourceAutoConfiguration) - Replacing Spring Security with a custom auth mechanism
- Replacing the default
ObjectMapperconfiguration
Configuration Properties: How Auto-Config Reads Your Settings
Auto-configuration classes read your application.yml / application.properties via @ConfigurationProperties beans. These are type-safe property bindings:
// Spring Boot reads spring.datasource.* properties into this class
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
private String url;
private String username;
private String password;
private String driverClassName;
// getters and setters
}
You can write your own @ConfigurationProperties to bind your custom configuration in the same way:
@ConfigurationProperties(prefix = "myapp.cache")
@Validated
public class CacheProperties {
@NotNull
private Duration ttl = Duration.ofMinutes(10);
private int maxSize = 1000;
private boolean enabled = true;
// getters and setters
}
myapp:
cache:
ttl: 30m
max-size: 5000
enabled: true
Register it as a bean:
@Configuration
@EnableConfigurationProperties(CacheProperties.class)
public class CacheConfig { ... }
Or simply annotate it with @Component.
Writing Your Own Auto-Configuration
This is useful for internal libraries shared across multiple Spring Boot applications — instead of every app configuring the same beans, you write it once as a starter.
Step 1: The Auto-Configuration Class
package com.example.mylib.autoconfigure;
@AutoConfiguration
@ConditionalOnClass(MyServiceClient.class)
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // allows apps to override with their own bean
public MyServiceClient myServiceClient(MyServiceProperties props) {
return new MyServiceClient(props.getBaseUrl(), props.getApiKey());
}
}
Step 2: The Properties Class
@ConfigurationProperties(prefix = "myservice")
public class MyServiceProperties {
@NotEmpty
private String baseUrl;
private String apiKey;
// getters and setters
}
Step 3: Register the Auto-Configuration
Create the registration file in your library JAR:
src/main/resources/META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports
Contents:
com.example.mylib.autoconfigure.MyServiceAutoConfiguration
Step 4: The Starter POM (Optional but Recommended)
Create a separate my-service-spring-boot-starter module that just has dependencies:
<dependencies>
<!-- Your auto-configuration module -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-service-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
<!-- The actual library being configured -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-service-client</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
Any Spring Boot app that adds my-service-spring-boot-starter now automatically gets a configured MyServiceClient bean — no manual @Bean method needed — and can override it by defining their own.
Auto-Configuration Order and Dependencies
Sometimes one auto-configuration depends on another. Use @AutoConfigureAfter and @AutoConfigureBefore to control ordering:
@AutoConfiguration
@AutoConfigureAfter(DataSourceAutoConfiguration.class) // run after DataSource is configured
public class FlywayAutoConfiguration {
@Bean
@ConditionalOnBean(DataSource.class)
public Flyway flyway(DataSource dataSource) {
return Flyway.configure().dataSource(dataSource).load();
}
}
Spring Boot 4: Modularisation Impact
In Spring Boot 4, the monolithic spring-boot-autoconfigure JAR was split into modules. The key change for custom starter authors: you now reference module-specific JARs instead of the monolithic one.
<!-- Spring Boot 3.x custom starter dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<!-- includes ALL auto-configs — you get the whole thing -->
</dependency>
<!-- Spring Boot 4.x custom starter — reference only what you need -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-jdbc</artifactId>
<!-- only the JDBC auto-config module -->
</dependency>
If your custom auto-configuration extends or conditionally depends on a built-in auto-config class, check that the specific module JARs are on the classpath, not just the monolithic one.
Common Auto-Configuration Pitfalls
1. Defining a bean that shadows auto-configuration but with the wrong type
@Bean
public DataSource dataSource() {
// This backs off DataSourceAutoConfiguration — as intended
return new HikariDataSource(config);
}
// But if you accidentally use the wrong type:
@Bean
public javax.sql.DataSource dataSource() { // interface vs impl
// This might NOT trigger @ConditionalOnMissingBean(DataSource.class)
// depending on how the condition resolves the type
}
Be explicit: match the type in your @Bean method to the type in @ConditionalOnMissingBean.
2. Auto-configuration in the wrong package
Auto-configuration classes must NOT be in the main application package (or a subpackage). Component scan would pick them up twice — once through @ComponentScan and once through auto-configuration. Put custom auto-configuration in a separate JAR.
3. Forgetting @AutoConfiguration (using @Configuration instead)
@Configuration classes found by component scan are processed differently from auto-configuration classes. Auto-configuration classes should use @AutoConfiguration (Spring Boot 2.7+), which is a specialised form of @Configuration that participates correctly in the ordering and exclusion mechanisms.
Quick Reference
| Goal | How |
|---|---|
| See what auto-configured | java -jar app.jar --debug → CONDITIONS EVALUATION REPORT |
| Disable a specific auto-config | @SpringBootApplication(exclude = {...}) or spring.autoconfigure.exclude |
| Override an auto-configured bean | Define your own @Bean of the same type |
| Custom auto-configuration | @AutoConfiguration class + AutoConfiguration.imports registration file |
| Type-safe config properties | @ConfigurationProperties(prefix = "...") |
| Control activation | @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty |
| Control order | @AutoConfigureAfter, @AutoConfigureBefore |
Auto-configuration is not magic — it is a well-structured conditional bean registration system. Once you understand conditions, the registration file, and the non-invasive override principle, you can predict exactly what Spring Boot will and will not create for any combination of dependencies and properties.
