Auto-configuration is Spring Boot's mechanism for automatically configuring beans
based on what is on the classpath, what beans already exist, and what properties are set.
It is what lets a Spring Boot app run with almost no explicit @Bean definitions.
// Just add spring-boot-starter-web to the classpath and Spring Boot
// auto-configures: an embedded Tomcat, a DispatcherServlet, a Jackson
// ObjectMapper, an error page, and more — with zero @Bean methods.
@SpringBootApplication // ← bundles @EnableAutoConfiguration
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Auto-configuration is opinionated but backs off — the moment you define your own bean of a given type, Spring Boot's default for that type steps aside. This "convention over configuration, but you can always override" model is the core of Spring Boot.
Rule of thumb: Auto-configuration = "sensible defaults wired from the classpath"; you only write configuration for the things you want to differ from those defaults.
@EnableAutoConfiguration imports AutoConfigurationImportSelector, which reads a
list of candidate auto-configuration class names from a well-known file on the classpath
and then filters them by their @Conditional annotations.
// Spring Boot 2.7+ reads this file from every jar on the classpath:
// META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
//
// (Before 2.7 it read the "EnableAutoConfiguration" key in:
// META-INF/spring.factories)
//
// Each line is a fully-qualified class name, e.g.:
// org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
The selector loads all candidates, then applies each class's conditions
(@ConditionalOnClass, @ConditionalOnMissingBean, etc.). Only the ones whose
conditions pass actually contribute beans. This two-step "list, then filter" is why
adding a starter jar is enough to switch features on.
Rule of thumb: @EnableAutoConfiguration doesn't scan your code — it reads
AutoConfiguration.imports from library jars and lets @Conditional decide what applies.
@SpringBootApplication is a convenience meta-annotation that bundles the three
annotations almost every app needs, so you don't repeat them.
// @SpringBootApplication is equivalent to:
@SpringBootConfiguration // a specialization of @Configuration
@EnableAutoConfiguration // turn on auto-configuration
@ComponentScan // scan this package and sub-packages for @Component etc.
public class App { }
@SpringBootConfiguration marks the class as a source of bean definitions (and lets
the test framework find it). @EnableAutoConfiguration enables the auto-config engine.
@ComponentScan discovers your own @Component, @Service, @RestController, etc.
Because component scanning starts in the annotated class's package, the main class
should sit in a root package above all your code.
Rule of thumb: Put your @SpringBootApplication class in the top-level package —
everything below it is component-scanned automatically.
Auto-configuration classes are gated by @Conditional annotations that decide, at
startup, whether a configuration or bean should be created.
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // only if DataSource is on the classpath
public class MyDataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // back off if the user defined one
@ConditionalOnProperty(name = "app.db.enabled", havingValue = "true")
DataSource dataSource() { /* ... */ }
}
The common ones: @ConditionalOnClass / @ConditionalOnMissingClass (classpath
presence), @ConditionalOnBean / @ConditionalOnMissingBean (bean presence),
@ConditionalOnProperty (config flag), @ConditionalOnWebApplication (servlet vs
reactive vs none), and @ConditionalOnResource (a file exists). They all build on the
lower-level @Conditional(Condition.class) SPI.
Rule of thumb: @ConditionalOnClass decides if a library is present;
@ConditionalOnMissingBean decides whether you already overrode it — together they
make auto-config both classpath-driven and user-overridable.
Most auto-configured beans are annotated @ConditionalOnMissingBean, meaning "only
create this default if the application hasn't already provided one." Defining your own
bean of the same type makes Spring Boot's default silently back off.
@Configuration
public class JacksonConfig {
// Spring Boot auto-configures an ObjectMapper, but it's marked
// @ConditionalOnMissingBean — so THIS one wins and the default backs off.
@Bean
ObjectMapper objectMapper() {
return new ObjectMapper()
.findAndRegisterModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}
Ordering matters: user configuration is evaluated before auto-configuration (auto-
config classes run last, via @AutoConfiguration ordering), so by the time the default
bean's condition is checked, your bean already exists and the condition fails.
Rule of thumb: To customize an auto-configured component, just declare your own
@Bean of that type — no need to disable anything; @ConditionalOnMissingBean does it.
A starter is a curated, empty "bag of dependencies" — a POM that pulls in a consistent, version-aligned set of libraries for a feature. Starters bring the jars; auto-configuration reacts to those jars being present.
<!-- This one line pulls in Spring MVC, embedded Tomcat, Jackson, validation, etc. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- The starter itself has almost no code — its job is to declare dependencies.
Because Tomcat + Spring MVC are now on the classpath, the matching
@ConditionalOnClass auto-configurations activate automatically. -->
Starters follow the naming convention spring-boot-starter-*. The versions come from
the Spring Boot BOM (bill of materials) inherited via the parent POM, so you rarely
specify versions yourself.
Rule of thumb: Starters answer "what's on the classpath"; auto-configuration answers "what to do about it." Add a starter, get a working feature.
Spring Boot can print a condition evaluation report showing every auto-config class and why it matched ("Positive matches") or didn't ("Negative matches").
# application.properties — log the report at startup:
debug=true
// Or, at runtime, hit the Actuator endpoint (needs spring-boot-starter-actuator):
// GET /actuator/conditions
// Returns JSON of positiveMatches / negativeMatches / unconditionalClasses.
//
// You can also start with --debug on the command line:
// java -jar app.jar --debug
The report tells you exactly why something did or didn't configure — e.g. "DataSourceAutoConfiguration did not match: @ConditionalOnClass found required class 'javax.sql.DataSource' but ... no DataSource bean URL property." This is the first tool to reach for when "Spring Boot isn't configuring the thing I expect."
Rule of thumb: When auto-config does something surprising, set debug=true (or call
/actuator/conditions) and read the positive/negative matches before guessing.
Sometimes an auto-configuration activates that you don't want (a classic case is
DataSourceAutoConfiguration firing before you've configured a database). You can
exclude it by class or by name.
// Option 1 — on the application class:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class App { }
# Option 2 — in properties (use the fully-qualified name, by string):
spring.autoconfigure.exclude=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Use exclude for classes on the classpath; use excludeName (or the property form)
when the class might not be present at compile time. Excluding is a blunt instrument —
prefer overriding a single bean with @ConditionalOnMissingBean semantics when you only
need to change part of the behavior.
Rule of thumb: Exclude a whole auto-configuration only when you genuinely don't want the feature; to merely tweak it, override the specific bean instead.
@Configuration marks any class that defines @Bean methods. @AutoConfiguration is a
specialized form (Spring Boot 2.7+) used only by library auto-configuration classes
listed in AutoConfiguration.imports, and it adds ordering and proxy semantics suited to
that role.
// Library code — a real auto-configuration:
@AutoConfiguration(after = DataSourceAutoConfiguration.class) // ordering hints
@ConditionalOnClass(JdbcTemplate.class)
public class JdbcTemplateAutoConfiguration {
@Bean
@ConditionalOnMissingBean
JdbcTemplate jdbcTemplate(DataSource ds) { return new JdbcTemplate(ds); }
}
// Your app code — ordinary configuration:
@Configuration
public class AppConfig { @Bean MyService myService() { return new MyService(); } }
@AutoConfiguration is meta-annotated with @Configuration(proxyBeanMethods = false)
(lightweight, no CGLIB proxying) and supports before/after/beforeName/afterName
to order auto-config classes relative to each other. You use @Configuration in apps;
@AutoConfiguration is for the libraries you build.
Rule of thumb: Write @Configuration in your application; reserve @AutoConfiguration
for reusable starter libraries that register themselves via AutoConfiguration.imports.
Order matters when one auto-config's beans depend on another's. Spring Boot provides ordering hints rather than a global sequence — you express "before" and "after" relationships.
@AutoConfiguration(
after = DataSourceAutoConfiguration.class, // run after the DataSource is configured
before = TransactionAutoConfiguration.class) // but before transactions are set up
public class MyOrmAutoConfiguration { /* needs a DataSource to exist first */ }
// Use the *Name variants when the other class may be absent at compile time:
@AutoConfiguration(afterName =
"org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration")
class AnotherAutoConfiguration { }
There are also @AutoConfigureBefore, @AutoConfigureAfter, and @AutoConfigureOrder
annotations (the older style). Note that auto-configurations as a group always run
after your own user-defined @Configuration, which is what makes
@ConditionalOnMissingBean back-off work.
Rule of thumb: Don't try to impose a total order — declare only the before/after
edges your beans actually need, and let Spring Boot topologically sort the rest.
Conditions are evaluated during the bean definition registration phase of context
refresh, before beans are instantiated. Class-presence conditions (@ConditionalOnClass)
are checked using ASM bytecode inspection, not by loading the class — so a missing
class doesn't throw NoClassDefFoundError.
// This is safe even if 'com.example.Optional' is NOT on the classpath:
@ConditionalOnClass(name = "com.example.OptionalLibrary")
@AutoConfiguration
public class OptionalAutoConfiguration {
// The condition is read from annotation metadata via ASM, so the JVM
// never tries to link OptionalLibrary unless the condition passes.
}
Because conditions run at definition time, @ConditionalOnBean is order-sensitive: the
bean it looks for must already be defined when the condition is evaluated, which is why
@ConditionalOnBean is mainly reliable inside auto-configuration (ordered after user
config) rather than between two arbitrary user beans.
Rule of thumb: @ConditionalOnClass is safe for optional libraries (ASM-based, no
linking); @ConditionalOnBean only sees beans defined before it — mind the ordering.
To make a reusable library that configures itself when added to any Spring Boot app,
write an @AutoConfiguration class and register it in the imports file.
// 1. The auto-configuration 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 (src/main/resources/META-INF/spring/
// org.springframework.boot.autoconfigure.AutoConfiguration.imports):
// com.example.greeting.GreetingAutoConfiguration
Any app that adds your jar now gets a GreetingService automatically, overridable via
@ConditionalOnMissingBean and configurable via GreetingProperties. This is exactly
how official starters work. Package the auto-config separately from your "starter" POM by
convention (acme-spring-boot-autoconfigure + acme-spring-boot-starter).
Rule of thumb: Custom auto-config = @AutoConfiguration class + a line in
AutoConfiguration.imports + @ConditionalOnMissingBean so consumers can override.
Auto-configurations expose tunables through @ConfigurationProperties classes that
bind external properties to typed Java objects, so users configure behavior without
touching code.
@ConfigurationProperties(prefix = "app.greeting")
public class GreetingProperties {
private String prefix = "Hello"; // default if app.greeting.prefix is unset
private int repeat = 1;
// getters/setters — binder uses them (or use a record + constructor binding)
}
// The auto-config enables and injects it:
@AutoConfiguration
@EnableConfigurationProperties(GreetingProperties.class)
public class GreetingAutoConfiguration { /* inject GreetingProperties into beans */ }
# User overrides in application.properties:
app.greeting.prefix=Hi
app.greeting.repeat=3
This is the standard pattern: the library ships defaults, the user overrides via
properties/YAML/env vars, and relaxed binding maps APP_GREETING_PREFIX (env) to
app.greeting.prefix automatically.
Rule of thumb: Expose every knob through @ConfigurationProperties, never hard-code —
it's how auto-config stays "opinionated defaults you can always override."
A FailureAnalyzer turns an ugly startup stack trace into a readable diagnostic with
a probable cause and an action. Spring Boot ships many; libraries can add their own. It's
the reason a misconfigured port or missing datasource prints a clean explanation.
public class PortInUseFailureAnalyzer
extends AbstractFailureAnalyzer<PortInUseException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
return new FailureAnalysis(
"Web server failed to start. Port " + cause.getPort() + " was already in use.",
"Identify and stop the process on port " + cause.getPort()
+ ", or configure a different port with 'server.port'.",
cause);
}
}
// Registered in META-INF/spring.factories under
// org.springframework.boot.diagnostics.FailureAnalyzer
When startup fails, Spring Boot runs each analyzer; the first that matches the exception prints the friendly APPLICATION FAILED TO START banner instead of a raw trace.
Rule of thumb: Those clear "Description / Action" startup error blocks are
FailureAnalyzers — add one to your library to give users actionable config errors.
The embedded server is auto-configured by classpath presence, so you change it by changing dependencies — exclude Tomcat from the web starter and add the alternative.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion> <!-- drop the default Tomcat -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <!-- add Jetty; its @ConditionalOnClass auto-config now wins -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
No code changes are needed: ServletWebServerFactoryAutoConfiguration picks whichever
server class is on the classpath. This is auto-configuration's whole philosophy — the
classpath expresses intent, and the right beans follow.
Rule of thumb: To change an auto-configured infrastructure piece (server, JSON lib, connection pool), change the dependency on the classpath, not the Java code.
By default @Configuration classes are CGLIB-proxied so that calling one @Bean method
from another returns the shared singleton. That proxying has a startup cost. Auto-config
classes set proxyBeanMethods = false because they rarely call their own bean methods
inter-dependently — they take dependencies as method parameters instead.
@Configuration(proxyBeanMethods = false) // no CGLIB subclass — faster startup
public class FastConfig {
@Bean A a() { return new A(); }
// Get 'A' via a parameter (the container injects the singleton),
// NOT by calling a() directly — direct calls would create a new A
// because there's no proxy to intercept them.
@Bean B b(A a) { return new B(a); }
}
@AutoConfiguration already implies proxyBeanMethods = false. The trade-off: with
proxying off you must never invoke another @Bean method directly (it would bypass the
container and build a duplicate), so always inject collaborators as parameters.
Rule of thumb: proxyBeanMethods = false = faster, lighter config — fine as long as
@Bean methods receive their dependencies as parameters rather than calling each other.
More Core interview questions
More ways to practice
The self-quiz is live. Join our channel for updates, new content & tech tips.