Why auto-configuration is the question behind the question
"How does Spring Boot configure all this for me?" is the single most revealing Spring Boot interview topic. A candidate who can explain auto-configuration understands the classpath, conditional beans, bean override semantics, and how to debug a misbehaving context — which is most of what senior Spring work actually requires. This article builds the whole mental model.
The one-sentence definition
Auto-configuration registers beans based on what is on the classpath, what beans already exist, and what properties are set — and it always backs off the moment you provide your own version of something.
@SpringBootApplication // bundles @EnableAutoConfiguration
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Add spring-boot-starter-web, and without writing a single @Bean you get an embedded Tomcat,
a DispatcherServlet, JSON serialization via Jackson, sensible error handling, and more.
Step 1 — @SpringBootApplication unpacks into three annotations
// @SpringBootApplication ==
@SpringBootConfiguration // a @Configuration that tests can locate
@EnableAutoConfiguration // turn the auto-config engine on
@ComponentScan // discover your own @Component/@Service/@RestController
public class App { }
Component scanning starts in the annotated class's package, which is why the main class belongs in a root package above the rest of your code.
Step 2 — @EnableAutoConfiguration discovers candidates
@EnableAutoConfiguration imports AutoConfigurationImportSelector. That selector does not
scan your code. It reads a list of candidate class names from every jar on the classpath:
# Spring Boot 2.7+:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
# Before 2.7 (the "EnableAutoConfiguration" key in):
META-INF/spring.factories
Each line is one fully-qualified auto-configuration class name. Spring Boot's own
spring-boot-autoconfigure jar contributes well over a hundred of them.
Step 3 — @Conditional decides what survives
Loading the candidates is only half the story. Each class is gated by conditions, evaluated during bean-definition registration:
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // only if JDBC is on the classpath
public class MyDataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // back off if the app already defines one
@ConditionalOnProperty(name = "app.db.enabled", havingValue = "true")
DataSource dataSource() { /* ... */ }
}
The conditions you will see most:
| Condition | Fires when |
|---|---|
@ConditionalOnClass / @ConditionalOnMissingClass | A class is / isn't on the classpath |
@ConditionalOnBean / @ConditionalOnMissingBean | A bean is / isn't already defined |
@ConditionalOnProperty | A property has a given value |
@ConditionalOnWebApplication | The app is servlet / reactive / none |
@ConditionalOnResource | A resource file exists |
A subtle but important detail: @ConditionalOnClass is evaluated by ASM bytecode inspection,
not by loading the class. A missing optional library therefore never throws
NoClassDefFoundError — the condition simply evaluates to false.
Step 4 — starters bring the classpath, auto-config reacts
A starter is a dependency aggregator with almost no code of its own:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
It pulls in Spring MVC, Tomcat, Jackson and validation at versions aligned by the Spring Boot
BOM. Now that those classes exist on the classpath, the matching @ConditionalOnClass
auto-configurations activate. Starters express intent; auto-configuration acts on it.
Overriding: just define your own bean
Because user @Configuration is processed before auto-configuration, and auto-config beans are
marked @ConditionalOnMissingBean, overriding is effortless:
@Configuration
public class JacksonConfig {
@Bean
ObjectMapper objectMapper() { // Spring Boot's default ObjectMapper now backs off
return new ObjectMapper()
.findAndRegisterModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}
You rarely need to exclude an auto-configuration just to tweak it. Exclusion is for when you want the whole feature gone:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class App { }
Debugging: read the condition evaluation report
When auto-config does something surprising, don't guess — ask Spring Boot to explain itself:
# application.properties
debug=true
This prints Positive matches (what applied and why) and Negative matches (what backed off and why). The same data is available at runtime from Actuator:
GET /actuator/conditions
A typical negative match reads like: "DataSourceAutoConfiguration did not match: required class found but no 'spring.datasource.url' property." That one line usually ends the debugging session.
Controlling order
When one auto-config depends on another, express the edge — don't try to impose a global order:
@AutoConfiguration(
after = DataSourceAutoConfiguration.class, // a DataSource must exist first
before = TransactionAutoConfiguration.class) // but configure us before transactions
public class MyOrmAutoConfiguration { }
Use the *Name variants (afterName, beforeName) when the referenced class might be absent
at compile time.
Writing your own auto-configuration
To make a library that configures itself in any Spring Boot app:
// 1. The class:
@AutoConfiguration
@ConditionalOnClass(GreetingService.class)
@EnableConfigurationProperties(GreetingProperties.class)
public class GreetingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
GreetingService greetingService(GreetingProperties props) {
return new GreetingService(props.getPrefix());
}
}
// 2. Register it in src/main/resources/META-INF/spring/
// org.springframework.boot.autoconfigure.AutoConfiguration.imports
// com.example.greeting.GreetingAutoConfiguration
Expose every tunable through a @ConfigurationProperties class so consumers configure behavior
via properties, YAML or environment variables — never by editing code. By convention, ship the
@AutoConfiguration code in an *-autoconfigure module and a thin *-starter POM that depends
on it.
A note on @AutoConfiguration vs @Configuration
@AutoConfiguration is for libraries listed in AutoConfiguration.imports. It implies
@Configuration(proxyBeanMethods = false) for faster startup and adds before/after ordering
support. In your application you keep writing ordinary @Configuration. With
proxyBeanMethods = false you must inject collaborators as @Bean method parameters rather than
calling another @Bean method directly — there is no CGLIB proxy to return the singleton.
Recap
Auto-configuration is a four-part story: @SpringBootApplication enables it, an import selector
reads candidate classes from AutoConfiguration.imports, @Conditional annotations filter those
candidates against the classpath and existing beans, and starters supply the classpath that drives
the whole thing. Override by defining your own bean (@ConditionalOnMissingBean does the back-off),
debug with debug=true or /actuator/conditions, and package reusable configuration as an
@AutoConfiguration class registered in the imports file. Master this and most of Spring Boot
stops being magic.