How Spring Boot Auto-Configuration Works
Introduction
This tutorial explains auto-configuration, the feature that makes Spring Boot feel like magic - add a library to the build, and the beans it needs appear in your application with no configuration code. We will start with what auto-configuration is and the annotation that switches it on. We will then find the auto-configuration classes themselves, in theAutoConfiguration.imports files of Spring Boot's jars, and see how Spring Boot 4 splits them across modules. Next we will look at the conditions that decide which of them apply, print the condition evaluation report for a real application, and watch auto-configuration back off when we define our own bean. Lastly, we will exclude an auto-configuration, and write an auto-configuration of our own.(This tutorial assumes you know conditional beansClick to read tutorial: Spring @Qualifier, @Primary and Conditional Beans and @Configuration classesClick to read tutorial: Spring Beans: @Component, @Bean and @Configuration from the Spring Core module - auto-configuration is built from both.)
What is auto-configuration?
In the first Spring Boot applicationClick to read tutorial: Your First Spring Boot Application with Spring Initializr, adding one dependency - the web starter - was enough to get an embedded Tomcat server, Spring MVC and JSON conversion, all configured and running. Nobody wrote a@Bean method for Tomcat. Those beans came from auto-configuration: ordinary @Configuration classes, shipped inside Spring Boot's jars, that Spring Boot loads automatically. Each one declares the beans for one technology, guarded by conditions -
- Is the technology on the classpath? Tomcat's configuration applies only if Tomcat's classes are present.
- Has the application already defined this bean? If you declare your own bean of the same type, Boot's version backs off.
- Do the properties ask for it? Some configurations are switched on or off by properties such as
spring.jmx.enabled.
Where it starts: @SpringBootApplication
The annotation on every Spring Boot main class is a combination of three others -What @SpringBootApplication is made of (simplified)
@SpringBootConfiguration // a @Configuration: the class can declare @Bean methods
@EnableAutoConfiguration // load the auto-configuration classes
@ComponentScan // scan this class's package and its sub-packages
public @interface SpringBootApplication {
Class<?>[] exclude() default {};
// ...
}
@EnableAutoConfiguration imports a special selector class, AutoConfigurationImportSelector, which collects the names of all auto-configuration classes from the classpath and hands them to the container to process like any other configuration - after the application's own configuration, so that conditions such as "has the application defined this bean?" can be answered.
Finding the auto-configuration classes
Every jar that contributes auto-configuration lists its classes in a text file atMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, one class name per line. The program below finds every such file in the jars on the classpath and counts the entries in each -
Listing the AutoConfiguration.imports files on the classpath
//ImportsApp.java
package com.javabrahman.boot.autoconfig.imports;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
public class ImportsApp {
static final String IMPORTS = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports";
public static void main(String[] args) throws IOException {
Enumeration<URL> files = ImportsApp.class.getClassLoader().getResources(IMPORTS);
int total = 0;
while (files.hasMoreElements()) {
URL url = files.nextElement();
if (!url.getProtocol().equals("jar")) {
continue; // list the jars only, not this tutorial's own classes folder
}
List<String> classes;
try (InputStream in = url.openStream()) {
classes = new String(in.readAllBytes()).lines()
.map(String::strip).filter(line -> !line.isEmpty() && !line.startsWith("#")).toList();
}
String jar = url.toString().replaceAll(".*/([^/!]+\\.jar)!.*", "$1");
System.out.printf("%-48s %3d%n", jar, classes.size());
total += classes.size();
}
System.out.println("Auto-configuration classes in total: " + total);
}
}
spring-boot-starter plus the validation starter - it prints -
OUTPUT of the above codespring-boot-autoconfigure-4.1.2.jar 12 spring-boot-validation-4.1.2.jar 1 Auto-configuration classes in total: 13
spring-boot-starter-webmvc, the same program prints -
OUTPUT on the web application's classpathspring-boot-autoconfigure-4.1.2.jar 12 spring-boot-http-converter-4.1.2.jar 1 spring-boot-jackson-4.1.2.jar 1 spring-boot-servlet-4.1.2.jar 5 spring-boot-tomcat-4.1.2.jar 5 spring-boot-webmvc-4.1.2.jar 6 Auto-configuration classes in total: 30
Explanation of the output
- Up to Spring Boot 3, almost all auto-configuration lived in one big jar,
spring-boot-autoconfigure. Spring Boot 4 split it into modules:spring-boot-autoconfigurenow keeps only the general-purpose configurations (AOP, task execution and scheduling, SSL, JMX and so on), and each technology brings its own -spring-boot-validation,spring-boot-tomcat,spring-boot-webmvc,spring-boot-jacksonand the rest. - So the classpath itself decides what can be auto-configured: the web starter pulled in the web modules, and their auto-configurations came with them.
- Being listed does not mean being applied - each class is still checked against its conditions, as the next sections show.
The conditions
An auto-configuration class is a@Configuration class annotated with @AutoConfiguration, and its class and @Bean methods carry condition annotations. A simplified version of the auto-configuration for Spring's JMX support shows the typical shape -
The shape of an auto-configuration class (simplified)
@AutoConfiguration
@ConditionalOnClass(MBeanExporter.class) // the library is present
@ConditionalOnBooleanProperty(name = "spring.jmx.enabled") // and switched on
public final class JmxAutoConfiguration {
@Bean
@ConditionalOnMissingBean(value = MBeanExporter.class, search = SearchStrategy.CURRENT)
MBeanExporter mbeanExporter(...) { // unless the application has one
...
}
}
@ConditionalOnClass/@ConditionalOnMissingClass: a class is or is not on the classpath.@ConditionalOnBean/@ConditionalOnMissingBean: a bean of a type or name exists, or does not.@ConditionalOnPropertyand@ConditionalOnBooleanProperty: a property has a value.@ConditionalOnResource: a file such asmessages.propertiesexists.@ConditionalOnWebApplication,@ConditionalOnThreading(platform or virtual threads), and several more.
The condition evaluation report
Spring Boot records every condition it evaluates in aConditionEvaluationReport. Starting an application with the --debug argument (or debug=true in application.properties) prints the full report at startup, and Spring Boot Actuator exposes it at the /actuator/conditions endpoint. The program below reads the report in code, so that we can print a compact version of it -
Printing the condition evaluation report of a running application
//ReportApp.java
package com.javabrahman.boot.autoconfig.report;
import java.util.Map;
import java.util.TreeMap;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
@SpringBootApplication
public class ReportApp {
public static void main(String[] args) {
try (var context = SpringApplication.run(ReportApp.class, args)) {
ConditionEvaluationReport report = ConditionEvaluationReport.get(context.getBeanFactory());
Map<String, ConditionAndOutcomes> results = new TreeMap<>(report.getConditionAndOutcomesBySource());
System.out.println("MATCHED (applied):");
results.keySet().removeIf(source -> !source.startsWith("org.springframework")); // Spring Boot's own only
results.forEach((source, outcomes) -> {
if (outcomes.isFullMatch()) {
System.out.println(" " + shorten(source));
}
});
System.out.println("NOT MATCHED (skipped), with the reason:");
results.forEach((source, outcomes) -> {
if (!outcomes.isFullMatch()) {
System.out.println(" " + shorten(source));
outcomes.forEach(o -> {
if (!o.getOutcome().isMatch()) {
System.out.println(" " + o.getOutcome().getMessage());
}
});
}
});
}
}
static String shorten(String className) {
return className.replace("org.springframework.boot.", "").replace("autoconfigure.", "");
}
}
MATCHED (applied):
aop.AopAutoConfiguration
aop.AopAutoConfiguration$ClassProxyingConfiguration
availability.ApplicationAvailabilityAutoConfiguration#applicationAvailability
context.LifecycleAutoConfiguration#defaultLifecycleProcessor
context.PropertyPlaceholderAutoConfiguration#propertySourcesPlaceholderConfigurer
ssl.SslAutoConfiguration#sslBundleRegistry
task.TaskExecutionAutoConfiguration
task.TaskExecutorConfigurations$AsyncConfigurerConfiguration
task.TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration#simpleAsyncTaskExecutorBuilder
task.TaskExecutorConfigurations$TaskExecutorConfiguration
task.TaskExecutorConfigurations$TaskExecutorConfiguration#applicationTaskExecutor
task.TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration#threadPoolTaskExecutorBuilder
task.TaskSchedulingAutoConfiguration
task.TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration#simpleAsyncTaskSchedulerBuilder
task.TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration#threadPoolTaskSchedulerBuilder
validation.ValidationAutoConfiguration
validation.ValidationAutoConfiguration#defaultValidator
validation.ValidationAutoConfiguration#methodValidationPostProcessor
NOT MATCHED (skipped), with the reason:
admin.SpringApplicationAdminJmxAutoConfiguration
@ConditionalOnBooleanProperty (spring.application.admin.enabled=true) did not find property 'spring.application.admin.enabled'
aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration
@ConditionalOnClass did not find required class 'org.aspectj.weaver.Advice'
context.MessageSourceAutoConfiguration
ResourceBundle did not find bundle with basename messages
info.ProjectInfoAutoConfiguration#buildProperties
@ConditionalOnResource did not find resource '${spring.info.build.location:classpath:META-INF/build-info.properties}'
info.ProjectInfoAutoConfiguration#gitProperties
GitResource did not find git info at classpath:git.properties
jmx.JmxAutoConfiguration
@ConditionalOnBooleanProperty (spring.jmx.enabled=true) did not find property 'spring.jmx.enabled'
task.TaskExecutorConfigurations$AsyncConfigurerWrapperConfiguration
@ConditionalOnBean (types: org.springframework.scheduling.annotation.AsyncConfigurer; SearchStrategy: all) did not find any beans of type org.springframework.scheduling.annotation.AsyncConfigurer
task.TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration#simpleAsyncTaskExecutorBuilderVirtualThreads
@ConditionalOnMissingBean (types: org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder; SearchStrategy: all) found beans of type 'org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder' simpleAsyncTaskExecutorBuilder
task.TaskExecutorConfigurations$TaskExecutorConfiguration#applicationTaskExecutorVirtualThreads
@ConditionalOnThreading did not find VIRTUAL
task.TaskExecutorConfigurations$TaskExecutorContextPropagationConfiguration
@ConditionalOnClass did not find required class 'io.micrometer.context.ContextSnapshot'
task.TaskSchedulingAutoConfiguration#scheduledBeanLazyInitializationExcludeFilter
@ConditionalOnBean (names: org.springframework.scheduling.config.internalScheduledAnnotationProcessor; SearchStrategy: all) did not find any beans named org.springframework.scheduling.config.internalScheduledAnnotationProcessor
task.TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration#simpleAsyncTaskSchedulerBuilderVirtualThreads
@ConditionalOnMissingBean (types: org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder; SearchStrategy: all) found beans of type 'org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder' simpleAsyncTaskSchedulerBuilder
task.TaskSchedulingConfigurations$TaskSchedulerConfiguration
@ConditionalOnBean (names: org.springframework.scheduling.config.internalScheduledAnnotationProcessor; SearchStrategy: all) did not find any beans named org.springframework.scheduling.config.internalScheduledAnnotationProcessor
Explanation of the output
- Each line names a configuration class, or a
@Beanmethod after a#. The program keeps only Spring Boot's own entries and shortens their names by droppingorg.springframework.boot.. - The matched entries are the defaults this small application received without asking: a task executor and builders for executors and schedulers, a
PropertySourcesPlaceholderConfigurerfor${...}placeholders, AOP proxying, an SSL bundle registry, and a Bean Validation validator. - The skipped entries show the reasons in plain words:
AspectJAutoProxyingConfigurationbecause AspectJ is not on the classpath;MessageSourceAutoConfigurationbecause there is nomessages.properties; JMX because thespring.jmx.enabledproperty is not set; the virtual-thread executors because virtual threads are not switched on (spring.threads.virtual.enabled=truewould switch them on); the build and git information beans because those files do not exist. - When a bean you expect is missing, or an unexpected one appears, this report is the first place to look.
Backing off: defining your own bean
The matched list above includesapplicationTaskExecutor, a thread pool that Spring Boot creates for @Async methods and other background work. Its configuration is conditional on the application not having an Executor bean of its own. The program below runs the same check twice - once with Boot's default, once with an executor of our own -
com.javabrahman.boot.extra - a configuration class declaring our own Executor
//OwnExecutorConfig.java
package com.javabrahman.boot.extra;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OwnExecutorConfig {
@Bean
Executor reportExecutor() {
return Executors.newFixedThreadPool(2);
}
}
Spring Boot's applicationTaskExecutor backs off when the application defines an Executor
//BackOffApp.java
package com.javabrahman.boot.autoconfig.backoff;
import java.util.Arrays;
import java.util.concurrent.Executor;
import com.javabrahman.boot.extra.OwnExecutorConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class BackOffApp {
public static void main(String[] args) {
show("Boot's default:", SpringApplication.run(BackOffApp.class));
// OwnExecutorConfig is outside the scanned package, so it is added as a second configuration source
show("With our own Executor bean:", SpringApplication.run(new Class<?>[] {BackOffApp.class, OwnExecutorConfig.class}, args));
}
static void show(String label, ConfigurableApplicationContext context) {
try (context) {
System.out.println(label);
Arrays.stream(context.getBeanNamesForType(Executor.class))
.forEach(name -> System.out.println(" Executor bean '" + name + "' of type " + context.getType(name).getSimpleName()));
}
}
}
Boot's default: Executor bean 'applicationTaskExecutor' of type ThreadPoolTaskExecutor With our own Executor bean: Executor bean 'reportExecutor' of type ThreadPoolExecutor
Explanation of the code
- In the first run, the only
Executorwas Boot'sapplicationTaskExecutor, aThreadPoolTaskExecutor. - In the second run,
OwnExecutorConfigwas added as an extra configuration source, so the application definedreportExecutor. Boot's@ConditionalOnMissingBeanfound it and skipped its own executor entirely - the application now has exactly the one executor we declared. OwnExecutorConfiglives outside the package thatBackOffAppscans, so the first run does not see it; the second run passes it toSpringApplication.run()as an extra configuration source. In a real application you would simply declare the@Beanin a scanned configuration class.- This is the everyday way to customise Spring Boot: declare the bean you want, and the default steps aside. Many defaults can also be tuned through properties without replacing the bean, such as
spring.task.execution.pool.max-size.
Excluding an auto-configuration
Occasionally you want an auto-configuration not to run at all, even though its conditions match. There are two ways to exclude it -Excluding auto-configuration classes
// 1. On the main class
@SpringBootApplication(exclude = TaskSchedulingAutoConfiguration.class)
public class ShopApplication { ... }
// 2. In application.properties (useful per environment, without a code change)
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
Writing your own auto-configuration
Auto-configuration is not reserved for Spring Boot itself - any library can ship its own, and Spring Boot treats it the same way. Writing one also makes the mechanism concrete. Our "library" provides aGreeter bean, configurable by a property, that applications get automatically and can replace -
com.javabrahman.greeting - the library's class
//Greeter.java
package com.javabrahman.greeting;
public class Greeter {
private final String salutation;
public Greeter(String salutation) {
this.salutation = salutation;
}
public String greet(String name) {
return salutation + ", " + name + "!";
}
}
com.javabrahman.greeting - the library's auto-configuration
//GreetingAutoConfiguration.java
package com.javabrahman.greeting;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnBooleanProperty(name = "greeting.enabled", matchIfMissing = true)
public class GreetingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
Greeter greeter(@Value("${greeting.salutation:Hello}") String salutation) {
return new Greeter(salutation);
}
}
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports - registers the auto-configuration
com.javabrahman.greeting.GreetingAutoConfiguration
An application in another package that uses the library
//GreetingApp.java
package com.javabrahman.boot.autoconfig.custom;
import com.javabrahman.greeting.Greeter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GreetingApp {
public static void main(String[] args) {
try (var context = SpringApplication.run(GreetingApp.class, args)) {
var greeter = context.getBeanProvider(Greeter.class).getIfAvailable();
System.out.println(String.join(" ", args) + " -> "
+ (greeter == null ? "no Greeter bean" : greeter.greet("Deborah Sprightly")));
}
}
}
(no arguments) -> Hello, Deborah Sprightly!
--greeting.salutation=Namaste -> Namaste, Deborah Sprightly!
--greeting.enabled=false -> no Greeter bean
Explanation of the code
GreetingAppis incom.javabrahman.boot.autoconfig.custom, so component scanning never sees thecom.javabrahman.greetingpackage. TheGreeterbean came only through theAutoConfiguration.importsfile - exactly how Spring Boot's own auto-configurations reach your application.@AutoConfigurationmarks the class as an auto-configuration (it is a@ConfigurationwithproxyBeanMethods = false, and it lets you order auto-configurations relative to each other withbeforeandafter).- The property
greeting.salutationchanged the greeting with no code change. Withgreeting.enabled=false,@ConditionalOnBooleanPropertyskipped the whole class and there was noGreeterbean;matchIfMissing = truemakes the feature enabled by default. @ConditionalOnMissingBeanon the@Beanmethod means an application that declares its ownGreetergets that one instead, just as with the executor earlier.- The first run's arguments are the literal words "(no arguments)", which Spring Boot ignores because they are not
--name=valueoptions; they only label the output line. - In a real library, the auto-configuration would go in its own module, usually with a matching starter that pulls in the library and its dependencies - the subject of the next tutorialClick to read tutorial: Spring Boot Starters Explained with Examples.
Summary
In this tutorial we first saw what auto-configuration is - conditional configuration shipped in Spring Boot's jars - and that@SpringBootApplication switches it on through @EnableAutoConfiguration. We then found the auto-configuration classes in the AutoConfiguration.imports files and saw how Spring Boot 4 spreads them across technology modules, so the classpath decides what can be configured. Next we looked at the condition annotations and printed the condition evaluation report of a real application, with the reason for every skipped configuration. We then watched Boot's task executor back off when we declared our own, saw two ways to exclude an auto-configuration, and lastly wrote an auto-configuration of our own, controlled by properties and replaceable by the application. The next tutorial covers the dependencies that bring auto-configurations onto the classpath: Spring Boot startersClick to read tutorial: Spring Boot Starters Explained with Examples.Spring Boot Tutorials
Your First Spring Boot Application with Spring InitializrClick to read tutorial: Your First Spring Boot Application with Spring Initializr Spring Boot Starters Explained with ExamplesClick to read tutorial: Spring Boot Starters Explained with Examples application.properties vs application.yml in Spring BootClick to read tutorial: application.properties vs application.yml in Spring Boot Type-Safe Configuration with @ConfigurationPropertiesClick to read tutorial: Type-Safe Configuration with @ConfigurationProperties Spring Boot Profiles and Environment-Specific ConfigurationClick to read tutorial: Spring Boot Profiles and Environment-Specific Configuration Logging in Spring Boot: SLF4J, Logback and Log LevelsClick to read tutorial: Logging in Spring Boot: SLF4J, Logback and Log Levels Spring Boot DevTools: Automatic Restart and ProductivityClick to read tutorial: Spring Boot DevTools: Automatic Restart and Productivity
Your First Spring Boot Application with Spring InitializrClick to read tutorial: Your First Spring Boot Application with Spring Initializr Spring Boot Starters Explained with ExamplesClick to read tutorial: Spring Boot Starters Explained with Examples application.properties vs application.yml in Spring BootClick to read tutorial: application.properties vs application.yml in Spring Boot Type-Safe Configuration with @ConfigurationPropertiesClick to read tutorial: Type-Safe Configuration with @ConfigurationProperties Spring Boot Profiles and Environment-Specific ConfigurationClick to read tutorial: Spring Boot Profiles and Environment-Specific Configuration Logging in Spring Boot: SLF4J, Logback and Log LevelsClick to read tutorial: Logging in Spring Boot: SLF4J, Logback and Log Levels Spring Boot DevTools: Automatic Restart and ProductivityClick to read tutorial: Spring Boot DevTools: Automatic Restart and Productivity