When you start using Spring Boot, you might find yourself wondering: “Why does everything just work even though I haven’t configured anything?” You add a dependency, a Bean gets registered, and it simply works. It’s convenient, no doubt — but when something goes wrong, you need to be able to debug it.
This article walks through how AutoConfiguration works, following the startup sequence step by step. By the end, you’ll be able to confirm “why this Bean was registered” using logs, and write your own custom AutoConfiguration.
What Is AutoConfiguration?
AutoConfiguration is a mechanism that automatically registers Beans based on conditions such as the state of the classpath and property values. The reason all the necessary Beans are in place just by adding a dependency — without writing a single @Bean — is thanks to this mechanism.
@SpringBootApplication includes @EnableAutoConfiguration, which serves as the entry point for AutoConfiguration.
Overview of the Startup Sequence
Here is the general flow:
@EnableAutoConfigurationacts as the triggerAutoConfigurationImportSelectorloads the list of AutoConfiguration class candidates- The
@Conditionalannotations on each class are evaluated - Only the Beans from classes that satisfy the conditions are registered
Keeping this flow in mind makes the rest of the explanation much easier to follow.
Where Is the List of AutoConfiguration Class Candidates?
The list of AutoConfiguration classes is described in metadata files inside JAR files.
Up to Spring Boot 2.x, this was written in META-INF/spring.factories:
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration
From Spring Boot 3.x onward, it was migrated to a file called META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.example.MyAutoConfiguration
If both files coexist in an existing project it will still work, but for Spring Boot 3.x projects it is recommended to consolidate to the new format. If you unzip spring-boot-autoconfigure.jar and look inside, you’ll find hundreds of AutoConfiguration classes registered there.
The Role of AutoConfigurationImportSelector
@EnableAutoConfiguration uses AutoConfigurationImportSelector internally. This class reads the candidate list file, applies any exclusion settings, and narrows down the classes.
If you want to exclude a specific AutoConfiguration, you can specify it as follows:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class MyApplication { ... }
Alternatively, you can specify it in application.properties:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Conditional Bean Registration with @Conditional Annotations
AutoConfiguration classes are annotated with @Conditional-family annotations that determine “under what conditions Beans should be registered.” Here are the commonly used ones:
| Annotation | Condition |
|---|---|
@ConditionalOnClass | When the specified class is on the classpath |
@ConditionalOnMissingBean | When no Bean of the same type has been registered yet |
@ConditionalOnProperty | When the specified property is set (e.g., spring.xxx.enabled=true) |
@ConditionalOnWebApplication | When running as a web application |
When multiple @Conditional annotations are present, all conditions must be satisfied. They are evaluated with AND logic, so if even one condition is not met, the Bean will not be registered.
@ConditionalOnProperty is often used in combination with Profiles. For switching configurations per environment, see Spring Boot Profiles to Safely Switch Environment-Specific Configuration.
A Real-World Example: Reading DataSourceAutoConfiguration
Let’s look at actual code. The following is a simplified version for illustrative purposes (in the actual Spring Boot 3.x source, it is split into inner @Configuration classes called EmbeddedDatabaseConfiguration and PooledDataSourceConfiguration, but this is sufficient for understanding the mechanism):
@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DataSourceProperties properties) {
// Create and return a DataSource
}
}
There are two key points here:
@ConditionalOnClass(DataSource.class)enables this only when a JDBC driver is on the classpath@ConditionalOnMissingBeanskips registration if aDataSourceBean is already registered
Thanks to that second condition, if you define your own DataSource, the AutoConfiguration’s DataSource is ignored. “Your own configuration takes priority” is exactly how this mechanism works.
Reading the Conditions Evaluation Report with debug=true
When you run into “a Bean isn’t registered” or “something isn’t working for some reason,” debug=true comes to the rescue:
# application.properties
debug=true
On startup, the Conditions Evaluation Report is printed to the console:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes 'jakarta.sql.DataSource' (OnClassCondition)
Negative matches:
-----------------
MongoAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class
'com.mongodb.MongoClient' (OnClassCondition)
Positive matches lists the AutoConfigurations that were actually registered; Negative matches lists those that were skipped because they didn’t meet the conditions. When trying to find out “why this Bean is missing,” checking Negative matches is the key.
Controlling the Order in Which AutoConfigurations Are Applied
When multiple AutoConfigurations depend on each other, the order of application matters. In Spring Boot 3.x, the recommended approach is to specify this as an attribute of the @AutoConfiguration annotation:
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
public class MyRepositoryAutoConfiguration {
// Processed after DataSourceAutoConfiguration
}
In addition to after, there is also a before attribute. The standalone annotation forms @AutoConfigureAfter / @AutoConfigureBefore remain available for backward compatibility, but using the attribute form is cleaner for Spring Boot 3.x projects.
If you want to specify ordering numerically, @AutoConfigureOrder is available. Use it with constants like Ordered.HIGHEST_PRECEDENCE when you need your AutoConfiguration to be processed before Spring Boot’s built-in ones.
Creating a Custom AutoConfiguration
Finally, let’s actually create a custom AutoConfiguration. This example automatically registers a simple Bean called GreetingService:
// GreetingService interface and implementation
public interface GreetingService {
String greet(String name);
}
public class DefaultGreetingService implements GreetingService {
public String greet(String name) { return "Hello, " + name + "!"; }
}
The AutoConfiguration class looks like this:
@AutoConfiguration
public class GreetingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public GreetingService greetingService() {
return new DefaultGreetingService();
}
}
Register this class in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.example.autoconfigure.GreetingAutoConfiguration
That’s all it takes. Simply by adding this JAR as a dependency in another project, the GreetingService Bean is automatically registered. If the user defines their own GreetingService, the @ConditionalOnMissingBean will skip the automatic registration.
Summary
When something goes wrong, the quickest path is to first check the Conditions Evaluation Report with debug=true. Looking at Negative matches immediately tells you “why a Bean wasn’t registered.” Once you can write custom AutoConfiguration, you can take it further by bundling team-wide common configuration as a Starter.
Also check out What Is a Spring Boot Starter and What Is the @Configuration Annotation. If you want to switch AutoConfiguration behavior per environment, Profile-Based Environment Configuration is also a useful reference.