Auto-configuration is the heart of Spring Boot. It's what lets you add spring-boot-starter-web to your pom.xml and instantly get Tomcat, DispatcherServlet, Jackson, and a bunch of other beans — without a single line of configuration.
Let's break down how it works, down to the last detail.
5.1. Where the Entry Point Lives
You already know that @SpringBootApplication includes @EnableAutoConfiguration. Let's look at it:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
Two key points:
-
@AutoConfigurationPackage— remembers the main class's package (we covered this in Part 4). -
@Import(AutoConfigurationImportSelector.class)— imports the selector that does all the magic.
@Import is a Spring Framework mechanism that lets you add BeanDefinitions to the context programmatically, bypassing scanning. A selector implements ImportSelector and returns an array of class names to register.
5.2. AutoConfigurationImportSelector — the Master Conductor
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
Note: this is a DeferredImportSelector, not a plain ImportSelector. The difference is fundamental.
5.2.1. DeferredImportSelector vs ImportSelector
| Type | When it's processed |
|---|---|
ImportSelector |
Immediately, while parsing @Configuration
|
DeferredImportSelector |
After all regular @Configuration classes have been processed |
This guarantees that auto-configurations are processed after user-defined beans. That's exactly why @ConditionalOnMissingBean in auto-configurations works correctly — by the time they're processed, all of your @Bean methods are already registered.
5.2.2. selectImports() — the entry point
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
AutoConfigurationEntry autoConfigurationEntry =
getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
The getAutoConfigurationEntry() method is the central one. Let's break it down.
5.3. getAutoConfigurationEntry() — a Step-by-Step Breakdown
protected AutoConfigurationEntry getAutoConfigurationEntry(
AnnotationMetadata annotationMetadata) {
// 1. Check: is auto-configuration enabled at all?
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 2. Get the @EnableAutoConfiguration attributes (exclude, excludeName)
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 3. Load ALL candidates from the classpath
List<String> configurations = getCandidateConfigurations(
annotationMetadata, attributes);
// 4. Deduplicate
configurations = removeDuplicates(configurations);
// 5. Apply exclusions
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 6. Filter by conditions (ConfigurationClassFilter)
configurations = getConfigurationClassFilter().filter(configurations);
// 7. Publish the AutoConfigurationImportEvent
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
The key steps are 3 and 6.
5.4. Loading the Candidates — .imports Instead of spring.factories
5.4.1. A Bit of History
| Version | Mechanism |
|---|---|
| Spring Boot 1.x – 2.6 |
META-INF/spring.factories (the EnableAutoConfiguration key) |
| Spring Boot 2.7 | Both mechanisms in parallel (backward compatibility) |
| Spring Boot 3.0+ |
.imports only; spring.factories registration for auto-configurations removed |
This is confirmed by the official documentation and release notes: "Spring Boot 2.7 introduced a new META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports file for registering auto-configurations, while maintaining backwards compatibility with registration in spring.factories. With this release, support for registering auto-configurations in spring.factories has been removed in favor of the imports file."
5.4.2. What the .imports File Looks Like
The file path:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
The format — one line = one class:
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
...
For comparison, the old spring.factories used a key-value format:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
The advantages of .imports: simpler parsing, no escaping issues, better AOT compatibility.
5.4.3. ImportCandidates — the loader
Candidates are loaded via ImportCandidates.load():
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
String location = String.format(
"META-INF/spring/%s.imports", annotation.getName());
List<String> candidates = new ArrayList<>();
// ... reads all files at this location from all JARs
return new ImportCandidates(candidates);
}
Key point: ImportCandidates does not use SpringFactoriesLoader (which reads spring.factories). It's a separate mechanism optimized for AOT and native images.
5.4.4. @AutoConfiguration — the new annotation
Since Spring Boot 2.7, auto-configuration classes are marked with @AutoConfiguration instead of @Configuration:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore
@AutoConfigureAfter
public @interface AutoConfiguration {
@AliasFor(annotation = AutoConfigureBefore.class, attribute = "value")
Class<?>[] before() default {};
@AliasFor(annotation = AutoConfigureAfter.class, attribute = "value")
Class<?>[] after() default {};
}
Three key differences from @Configuration:
-
proxyBeanMethods = false— auto-configurations are not proxied with CGLIB. It's faster and avoids circular dependencies. - It carries
@AutoConfigureBefore/@AutoConfigureAfter— the ordering can be specified right in the annotation. - It's semantically separated from user-defined
@Configurationclasses.
5.5. Conditional Annotations (@Conditional*) — Filtering the Candidates
After all the candidates are loaded (there are 150+ of them in Spring Boot 3.x), filtering begins. This is where the conditional annotations come into play.
5.5.1. How It's Wired
The filtering happens in ConfigurationClassFilter:
class ConfigurationClassFilter {
private final List<AutoConfigurationImportFilter> filters;
List<String> filter(List<String> configurations) {
String[] candidates = configurations.toArray(new String[0]);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
for (AutoConfigurationImportFilter filter : this.filters) {
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
skip[i] = true;
skipped = true;
}
}
}
// ... returns the filtered list
}
}
AutoConfigurationImportFilter is an SPI interface whose implementations are loaded via spring.factories (yes, spring.factories is still used for internal SPIs — just not for auto-configuration registration). Spring Boot ships three filters:
| Filter | What it checks |
|---|---|
OnClassCondition |
@ConditionalOnClass / @ConditionalOnMissingClass
|
OnBeanCondition |
@ConditionalOnBean / @ConditionalOnMissingBean
|
OnWebApplicationCondition |
@ConditionalOnWebApplication / @ConditionalOnNotWebApplication
|
Important: this is the first stage of filtering — a coarse-grained pass. It only checks for the presence of classes and beans, without loading the auto-configurations themselves. Finer-grained checks (properties, resources, expressions) happen later, when ConfigurationClassParser processes each specific class.
5.5.2. The Main Conditional Annotations
@ConditionalOnClass / @ConditionalOnMissingClass
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // activates only if DataSource is on the classpath
public class DataSourceAutoConfiguration { }
The check happens via ASM — the class is not loaded into the JVM; only the .class file's metadata is read. That's why it's safe to reference classes that may not be present at runtime.
@ConditionalOnBean / @ConditionalOnMissingBean
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
return new HikariDataSource();
}
The key customization mechanism. If you've declared your own DataSource, the auto-configuration backs off and doesn't create its own. This is exactly why auto-configurations are processed as a DeferredImportSelector — so that all of your beans are already registered by the time the checks run.
Caveat:
@ConditionalOnMissingBeanonly works reliably for@Beanmethods and@Configurationclasses that are processed after yours. If you put it on a regular@Configuration, the result can be unpredictable — @conditionals are evaluated while parsing the@Configuration, and the parsing order may differ from the bean registration order.
@ConditionalOnProperty
@AutoConfiguration
@ConditionalOnProperty(
prefix = "spring.datasource",
name = "url",
matchIfMissing = false
)
public class DataSourceAutoConfiguration { }
Checks the value of a property. matchIfMissing = true means "activate if the property is missing entirely."
@ConditionalOnResource
@ConditionalOnResource(resources = "classpath:my-config.properties")
@ConditionalOnWebApplication / @ConditionalOnNotWebApplication
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnExpression
@ConditionalOnExpression("${my.feature.enabled:false} and '${my.mode}' == 'advanced'")
Uses SpEL. Not recommended for auto-configurations — it's slow and plays poorly with AOT.
5.5.3. ConfigurationPhase — a Subtlety
A @Conditional can be evaluated on two phases:
public enum ConfigurationPhase {
PARSE_CONFIGURATION, // while parsing @Configuration
REGISTER_BEAN // while registering beans
}
@ConditionalOnBean and @ConditionalOnMissingBean use REGISTER_BEAN, because they need to know which beans are already registered. The rest (@ConditionalOnClass, @ConditionalOnProperty) use PARSE_CONFIGURATION.
5.6. Auto-configuration Ordering
When you have 150+ auto-configurations, the order in which they're applied is critical. For example, DataSourceAutoConfiguration must come before JpaRepositoriesAutoConfiguration.
5.6.1. @AutoConfigureBefore / @AutoConfigureAfter
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
public class JpaRepositoriesAutoConfiguration { }
Or via the standalone annotations:
@AutoConfiguration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class JpaRepositoriesAutoConfiguration { }
These annotations don't guarantee an absolute order — they only define a relative one. Spring Boot sorts the dependency graph via AutoConfigurationSorter.
5.6.2. @AutoConfigureOrder
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class MyEarlyAutoConfiguration { }
The semantics are the same as @Order, but for auto-configurations. It's used when auto-configurations know nothing about each other and can't reference one another via before/after.
5.6.3. AutoConfigurationSorter — how the sorting works
class AutoConfigurationSorter {
void sort(List<String> classNames) {
// 1. Reads each class's metadata (ASM)
// 2. Builds the graph: A before B, A after C
// 3. Topological sort
// 4. Resolves conflicts via @AutoConfigureOrder
}
}
Priority: @AutoConfigureBefore / @AutoConfigureAfter > @AutoConfigureOrder.
5.7. Creating Your Own Auto-configuration
5.7.1. The auto-configuration class
@AutoConfiguration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyServiceProperties properties) {
return new MyService(properties.getEndpoint(), properties.getTimeout());
}
}
5.7.2. Properties
@ConfigurationProperties(prefix = "my.service")
public class MyServiceProperties {
private String endpoint = "http://localhost:8080";
private Duration timeout = Duration.ofSeconds(5);
// getters/setters
}
5.7.3. Registration in .imports
The file:
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
The content:
com.example.autoconfigure.MyServiceAutoConfiguration
5.7.4. The starter
So that users can add a single dependency, you ship a starter — an empty JAR that pulls in:
<dependency>
<groupId>com.example</groupId>
<artifactId>my-service-spring-boot-starter</artifactId>
</dependency>
Inside the starter's pom.xml:
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-service</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-service-spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
Naming convention: xxx-spring-boot-starter (for users) and xxx-spring-boot-autoconfigure (for the auto-configuration code).
5.8. Spring Boot 3 vs Spring Boot 4: Key Differences
5.8.1. Modularization of spring-boot-autoconfigure
This is the biggest change in Boot 4.
| Aspect | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
| Artifact | A single monolithic spring-boot-autoconfigure (2 MiB in 3.5) |
47 modular JARs, one per technology |
| Size | Everything in one place | Only the modules you need end up on the classpath |
| Web auto-configurations | In spring-boot-autoconfigure
|
In spring-boot-webmvc / spring-boot-webflux
|
| JPA auto-configurations | In spring-boot-autoconfigure
|
In spring-boot-data-jpa
|
| IDE hints | All classes of all technologies | Only the ones actually in use |
The official announcement explains the motivation: "Instead of a single, monolithic spring-boot-autoconfigure jar, we are now splitting functionality into small and more focused modules. This change is motivated by maintainability, clarity, and a leaner runtime footprint."
5.8.2. What Hasn't Changed
-
@EnableAutoConfiguration— works the same way. -
AutoConfigurationImportSelector— the same mechanism. -
.importsfiles — the same format. -
@Conditional*— the same annotations. -
@AutoConfigureBefore/@AutoConfigureAfter— the same semantics.
5.8.3. BeanRegistrar — the new alternative
In Spring Framework 7 / Boot 4, BeanRegistrar arrived — programmatic bean registration without @Bean methods:
public class MyBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
if (env.acceptsProfiles(Profiles.of("dev"))) {
registry.registerBean("myService", MyService.class,
spec -> spec.supplier(ctx -> new MyService("dev")));
}
}
}
It's registered via @Import(MyBeanRegistrar.class). This is closer to the AOT approach: beans are registered programmatically rather than via reflection.
5.8.4. Starters
| Boot 3 | Boot 4 |
|---|---|
spring-boot-starter-web |
spring-boot-starter-webmvc |
spring-boot-starter-webflux |
spring-boot-starter-webflux (unchanged) |
spring-boot-starter-data-jpa |
spring-boot-starter-data-jpa (unchanged) |
5.9. Diagram: the Full Path of Auto-configuration
@SpringBootApplication
└─ @EnableAutoConfiguration
└─ @Import(AutoConfigurationImportSelector.class)
│
└─ refresh() → invokeBeanFactoryPostProcessors()
└─ ConfigurationClassPostProcessor
└─ ConfigurationClassParser
└─ DeferredImportSelectorGroupingHandler
└─ AutoConfigurationImportSelector.selectImports()
│
├─ 1. isEnabled()?
├─ 2. getAttributes() → exclude/excludeName
├─ 3. getCandidateConfigurations()
│ └─ ImportCandidates.load()
│ └─ META-INF/spring/...AutoConfiguration.imports
│ (all JARs)
├─ 4. removeDuplicates()
├─ 5. getExclusions() → removeAll()
├─ 6. ConfigurationClassFilter.filter()
│ ├─ OnClassCondition
│ ├─ OnBeanCondition
│ └─ OnWebApplicationCondition
└─ 7. fireAutoConfigurationImportEvents()
│
▼
A list of class names
│
▼
ConfigurationClassParser processes
each class as a @Configuration
│
▼
@Bean methods → BeanDefinitions
@Conditional* → final filtering
5.10. Key Takeaways
-
@EnableAutoConfiguration→AutoConfigurationImportSelectoris the single entry point. The selector is aDeferredImportSelector, so auto-configurations are processed after user-defined beans. -
.importsfiles (Boot 3+) replacedspring.factoriesfor auto-configurations. The path:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. -
@AutoConfigurationis the new annotation for auto-configuration classes:proxyBeanMethods = false, carries@AutoConfigureBefore/After. -
@Conditional*annotations work in two passes: coarse-grained filtering viaAutoConfigurationImportFilter(by classes/beans), then fine-grained viaConfigurationClassParser. -
@ConditionalOnMissingBeanis the "back-off" mechanism: if you've declared your own bean, the auto-configuration backs off. - Ordering is defined by
@AutoConfigureBefore/@AutoConfigureAfter(relative) and@AutoConfigureOrder(absolute). Sorting is done byAutoConfigurationSorter. - Boot 4 — modularization: 47 modules instead of a single
spring-boot-autoconfigure. The auto-configuration mechanism itself hasn't changed, only the packaging. - Your own auto-configuration =
@AutoConfiguration+@ConditionalOnClass+@ConditionalOnMissingBean+ an.importsfile + a starter.
Top comments (0)