Auto-Configuration Internals: From spring.factories to AutoConfiguration.imports
The last two articles taught you to run something and to pry open the three-in-one annotation. This one answers the question that stalls beginners most: why does one added dependency hand me a DataSource, a transaction manager and a JSON converter? There is no magic here — just a plain assembly line: jars ship with a candidate list, Boot reads it at startup, asks each entry a few questions ("did you configure this already? do you have that class?"), and registers only the ones that pass. The article splits that line into four stages, then teaches one more skill: when something does not take effect, how to make Boot tell you why.
a fully finished apartment comes pre-fitted with plumbing, wiring and furniture — but the moment you place your own sofa in the living room, the fit-out team will not put theirs there. Auto-configuration follows exactly that etiquette. And @ConditionalOnMissingBean is the umbrella rule at the hotel front desk: the shop lends an umbrella only because the guest brought none; show up with your own and the assistant puts the rack away before you even ask. That is why Section 2 keeps stressing "deferred import": Boot waits until you have finished speaking before deciding whether to configure anything for you.

After this article you should be able to answer three questions:
- Which file does the candidate list come from, and at which version did
spring.factoriesbecomeAutoConfiguration.imports? - What must an auto-configuration class satisfy before it really takes effect, and where do I read the reason when it is skipped?
- What is the correct way to replace Boot's
ObjectMapper, and why is the overriding switch the wrong tool?
You create a Spring Boot project and add a single dependency to pom.xml:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency>In application.yml you write only three or four lines of database connection info. You configure no DataSource, no EntityManagerFactory, no PlatformTransactionManager. Yet after startup all of these beans are sitting happily in the container — @Autowired a DataSource and it just works.
There is a question worth asking here: the Spring IoC container only builds what you tell it to build; it does not conjure a connection pool out of thin air. So who registered these beans?
The answer is auto-configuration. It is not magic: it is a set of configuration classes written into the jar ahead of time, which only take effect when their conditions are met. This article takes the whole pipeline apart — who reads which file, who evaluates the conditions, and who finally puts the beans into the container.
"One line of dependency, a roomful of beans" is best ticked through yourself. This generator follows the same rules as the lesson text: tick Data JPA alone and watch it drag in spring-orm, hibernate-core and spring-boot-starter-jdbc by itself; then stack the MySQL driver and H2 on top and notice what two drivers on one classpath produces — that is exactly the scene in the Section 13 sandbox and the trace at the end of Section 14. Finally add RabbitMQ, then untick it again: the second experiment below shows you which gate removed it.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version> <!-- 版本由 BOM 统管,子依赖不写 version -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>demo-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Everything starts from that one @Import on @EnableAutoConfiguration from the previous article:
@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { }The class that does the work is AutoConfigurationImportSelector. Its skeleton looks roughly like this:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; // returns empty when spring.boot.enableautoconfiguration=false } AutoConfigurationEntry entry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(entry.getConfigurations()); } @Override public Class<? extends Group> getImportGroup() { return AutoConfigurationGroup.class; // group = deferred import, ordered after user config }}DeferredImportSelector: note the word Deferred. A plainImportSelectoris processed immediately, whereas this one is postponed until after all of the user's own@Configurationclasses have been processed. That detail is critical, and Section 7 shows what it buys usselectImports: the entry point. It first checks the master switch, then asksgetAutoConfigurationEntryfor the candidate listgetAutoConfigurationEntry: internally it does "gather candidates → deduplicate → filter exclusions → trim by conditions" and finally returns the array of configuration classes to import
Expanding getAutoConfigurationEntry, a full load is this sequence:
getCandidateConfigurations()obtains all candidate auto-configuration classes (the list file inside the jars)- Duplicates are removed (several jars may declare the same class) and anything the user excluded via
exclude/excludeName/spring.autoconfigure.excludeis dropped AutoConfigurationImportFilterperforms a fast preliminary filter (for exampleOnClassConditionreads the classpath directly and can eliminate a large batch of irrelevant classes, saving parsing cost)- The survivors go to
ConfigurationClassParserone by one - During parsing, the
@Conditionalconditions on the class and its methods are evaluated byConditionEvaluator - Passers are registered as
BeanDefinitions; failures are recorded in theConditionEvaluationReport - The container runs
refresh(), instantiating real beans from thoseBeanDefinitions
notice there are two filters. The first is AutoConfigurationImportFilter, which does not parse classes and only inspects the classpath — it is a performance optimization that "cuts fast". The second is condition evaluation, which actually reads annotations and checks whether beans exist. Understanding this division explains why a project without RabbitMQ on its classpath pays no parsing cost for Rabbit's configuration classes at startup.
The two gates are not built the same and cannot do the same things — here they are side by side:

Look at the last row of the right column: only the second gate leaves a "why" behind. That is why "why did it not apply" can only ever be answered from the --debug report and never from the candidate-list stage — classes removed by gate one do not even appear by name. Beginners read this as "the report is missing my class"; Section 13 meets the same trap again.
Where does the "candidate list" come from? There is a version watershed here. Before Spring Boot 2.7 it was spring.factories; 2.7 introduced the new style, and 3.0 removed the old mechanism entirely.
| Aspect | spring.factories (≤ 2.7) | AutoConfiguration.imports (2.7+) |
|---|---|---|
| File path | META-INF/spring.factories | META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
| Format | properties: one key mapping to a list of class names | plain text: one class name per line |
| Lookup | via the key org.springframework.boot.autoconfigure.EnableAutoConfiguration | the file name itself is the key |
| Loading API | SpringFactoriesLoader.loadFactoryNames | ImportCandidates.load(AutoConfiguration.class, ...) |
| Status | deprecated in 2.7, gone in 3.0 | the current standard |
The old style looked like this — a comma-separated class list continued with backslashes:
# META-INF/spring.factories (old)org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.example.demo.autoconfigure.MyServiceAutoConfiguration,\com.example.demo.autoconfigure.OtherAutoConfigurationThe new style is cleaner, one class per line, with no key to declare:
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (new)com.example.demo.autoconfigure.MyServiceAutoConfigurationcom.example.demo.autoconfigure.OtherAutoConfigurationTrap: when upgrading from 2.7 to 3.0, if a custom starter still registers its auto-configuration via spring.factories, those entries silently stop working — no error, the classes simply are never loaded again. Search your upgrade checklist for spring.factories. As an aside, spring.factories was not fully deleted in 3.0; it still carries other extension points such as listeners and initializers. Only auto-configuration moved out.
With the pipeline understood, let's look at a real player. Spring Boot's own DataSourceAutoConfiguration (lightly trimmed) uses nearly every key annotation in the toolbox:
@AutoConfiguration(before = HibernateJpaAutoConfiguration.class)@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")@EnableConfigurationProperties(DataSourceProperties.class)@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class })public class DataSourceAutoConfiguration { @Configuration(proxyBeanMethods = false) @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class }) protected static class PooledDataSourceConfiguration { }}Annotation by annotation — this is how you should read auto-configuration source:
@AutoConfiguration: the new marker. It is equivalent to@Configuration(proxyBeanMethods = false)but additionally gains ordering (before/after). Legacy auto-configuration used@Configuration; new code should standardize on thisbefore = HibernateJpaAutoConfiguration.class: declares "I must run before the JPA auto-configuration". JPA needs a DataSource, so the wrong order makes JPA fail to find one — exactly the topic of Section 5@ConditionalOnClass({ DataSource.class, ... }): these classes must exist on the classpath. Withoutspring-jdbcthe whole configuration class is skipped and there will be no DataSource in the container@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory"): if the project uses reactive R2DBC, let R2DBC handle it and get out of the way@EnableConfigurationProperties(DataSourceProperties.class): bindsspring.datasource.*into a Java object for the@Beanmethods below@Import(...): imports finer-grained configuration. Auto-configuration is nested and layered: the outer class decides "should we configure a DataSource", the inner one decides "which connection pool"
Auto-configuration classes are not peers — they have dependencies. The classic pair: DataSourceAutoConfiguration must run before HibernateJpaAutoConfiguration. If JPA runs first and looks for a DataSource while none is registered yet, startup fails with "no DataSource found".
Spring Boot offers three ways to express order:
// Way one: before / after on @AutoConfiguration (most common, declares a dependency)@AutoConfiguration(before = HibernateJpaAutoConfiguration.class)public class DataSourceAutoConfiguration { }// Way two: after on the class — wait for the other to finish@AutoConfiguration(after = DataSourceAutoConfiguration.class)@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class })public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { // here we can safely assume the DataSource is ready}// Way three: @AutoConfigureOrder — an absolute number, lower runs first@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)public class MyEarlyAutoConfiguration { }before/afterare relative declarations meaning "I depend on so-and-so". Cycles are not allowed and fail at startup@AutoConfigureOrderis an absolute declaration using a number. It is more forceful but drifts toward magic numbers, so before/after is preferred- Fallback rule: after resolving all before/after relations,
AutoConfigurationSorterarranges the remaining classes with no dependency relation in alphabetical order (A→Z). This makes ordering deterministic — the same list yields the same order every startup, never jittering across JVMs
Key point: if asked "how is the order of auto-configuration classes decided", the standard answer has three layers — first resolve dependency order from before/after, then use @AutoConfigureOrder numbers for the ones without dependencies, and finally fall back to alphabetical order. Answer all three and you have full marks.
Those three layers are not parallel rules; they are a pipeline where each stage silences the ones below it. Click through it and pay attention to box 4 — it is the unglamorous alphabetical fallback that makes "the same pom behaves the same on every machine" true:
Auto-configuration decides "should I take effect" through conditional annotations. The table below collects the most common ones; the next article covers each in depth:
| Condition annotation | What it checks | Typical use |
|---|---|---|
@ConditionalOnClass | the class is on the classpath | wire only when the dependency is present (e.g. HikariCP) |
@ConditionalOnMissingClass | the class is absent | rule out one implementation |
@ConditionalOnBean | a bean of the type already exists | wire only when another bean exists |
@ConditionalOnMissingBean | no bean of the type exists | the fallback when the user configured nothing |
@ConditionalOnSingleCandidate | exactly one candidate bean | a single DataSource or transaction manager |
@ConditionalOnProperty | a property equals / differs from a value | feature toggles (like spring.aop.auto) |
@ConditionalOnWebApplication | the app is a web application | web-related auto-configuration |
@ConditionalOnExpression | a SpEL expression is true | complex combined conditions |
treat this table as a dictionary for now. The genuinely hard part is their evaluation order — @ConditionalOnBean depends on someone else having registered already, so how is "someone else goes first" guaranteed? That is the core question the next article answers.
When auto-configuration "does not take effect", guessing is the worst move. Spring Boot ships a condition evaluation report that prints every auto-configuration class's decision and the reason. Enable it in one of three ways:
# Way one: a startup argumentjava -jar app.jar --debug# Way two: configuration filedebug: true# Way three: only turn on the auto-configuration package loglogging: level: org.springframework.boot.autoconfigure: DEBUGOnce on, the console prints a report whose heart is two sections — "who took effect" and "who was skipped, and why":
============================CONDITIONS EVALUATION REPORT============================Positive matches:----------------- DataSourceAutoConfiguration matched: - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition) - @ConditionalOnMissingBean (types: io.r2dbc.spi.ConnectionFactory) did not find any beans (OnBeanCondition)Negative matches:----------------- RabbitAutoConfiguration matched: - @ConditionalOnClass did not find required class 'com.rabbitmq.client.Channel' (OnClassCondition)Exclusions:----------- DataSourceAutoConfiguration (excluded by @SpringBootApplication exclude)- Positive matches: these classes passed their conditions and really took effect. Each line lists the checks passed, effectively telling you "these beans come from here"
- Negative matches: these were skipped, with the reason in parentheses. When troubleshooting auto-configuration, about 80% of your time is spent reading this section — if you find
RabbitAutoConfigurationskipped because it "did not findcom.rabbitmq.client.Channel", the root cause is a missing dependency - Exclusions: entries you deliberately excluded are listed separately, confirming your exclude took effect
Note: the report comes from a ConditionEvaluationReport object that records every condition evaluation result; --debug merely renders it as readable text. Understand this and you know the report is not "a log" but a snapshot of the container's internal decision process.

Of the three ways to turn the report on, the first (--debug as a launch argument) is fine for one local glance. Real diagnosis needs one of the other two, because only those can vary per environment. Use the generator like this: tick logging first and turn on DEBUG for the auto-configuration package alone — far quieter than a blanket debug: true, and it does not drown the rest of the output; then add the datasource block, since those url / username / driver lines are the remedy for Section 8 and for the trace at the end of Section 14; finally add Profile to move both groups into a per-environment document and get the shape most teams actually keep: noisy in development, quiet in production.
server:
port: 8080
spring:
application:
name: demo-service
logging:
level:
root: INFO
com.example.demoservice: DEBUG
org.springframework.jdbc.core.JdbcTemplate: DEBUG # 打 SQL 与参数
file:
name: logs/app.log
logback:
rollingpolicy: { max-file-size: 50MB, max-history: 14 }
Auto-configuration is a "fallback when you configured nothing", so what is the right way to take over? First, a bad example: some people turn on this switch to make their bean win —
# ❌ not recommended: turn on BeanDefinition overridingspring.main.allow-bean-definition-overriding=trueSince Spring Boot 2.1 this defaults to false (overriding not allowed). Turning it on seems to "override everything", but it turns "who wins" into a question of registration order: whether the auto-configuration class or your class registers first is undetermined, so you may get your implementation or Boot's default — an extremely hard-to-reproduce latent bug.
The right approach uses the fact that auto-configuration steps aside on its own. Almost every auto-configuration class carries @ConditionalOnMissingBean:
@Configurationpublic class MyDataSourceConfig { // As soon as you define a DataSource, DataSourceAutoConfiguration will step // aside thanks to @ConditionalOnMissingBean and never conflict with your bean @Bean @ConfigurationProperties("spring.datasource.hikari") public DataSource dataSource(DataSourceProperties props) { return props.initializeDataSourceBuilder() .type(HikariDataSource.class) .build(); }}Why does this work? The clue planted back in Section 2: AutoConfigurationImportSelector is a DeferredImportSelector, postponed until after all user configuration has been processed. So when the auto-configuration class evaluates @ConditionalOnMissingBean, your DataSource is already registered, the condition reports "already present", and auto-configuration steps aside. Deferred import is precisely the technical guarantee of "user first".
allow-bean-definition-overriding=true masks a conflict rather than resolving it. It makes you believe the override succeeded while planting an order-dependency landmine. To override auto-configuration with your own bean, rely on the @ConditionalOnMissingBean hand-off first; only when you cannot conveniently define a bean of the same type (say you want to change a Builder rather than the final object) should you switch to the official Customizer extension points.
"Deferred import → you register first → it evaluates afterwards → it steps aside" is exactly this six-frame sequence:

Now the same thing as a stepped run. Six lines on the left, and on the right the two cells that decide everything: whether the container already holds a dataSource, and who is evaluating. Press step through beats 3 and 4 — the half-second of ordering between them is the entire technical content of "user first":
// the container parses your MyDataSourceConfig (a plain @Configuration, never deferred)// the BeanDefinition for @Bean dataSource is registered; no object built yet// only now does DeferredImportSelector run: the auto-config candidates arrive// DataSourceAutoConfiguration evaluates @ConditionalOnMissingBean(DataSource.class)// the condition sees one already present -> false, the inner configuration is not registered// refresh() instantiates: the container's single DataSource is your HikariDataSource| being processed | MyDataSourceConfig |
| which batch | the user's own configuration |
| deferred queue | not started yet |
ConfigurationClassParser.parseplain @Configuration branchThe demo below turns auto-configuration condition evaluation into switchable knobs. Toggle the three condition switches and watch, per auto-configuration bean, when it is created and when it is skipped wholesale:
Enough buttons — this article's central question ("did it apply, and if not why") has three direct answers on the command line: conditions prints the evaluation record, cond flips one condition by hand, and restart walks the whole assembly chain again.
Type them in this order; the contrast between steps 4 and 5 is exactly the contrast between the two gates in Section 2:
boot— build the container, read the wiring logconditions— who is in, who is out, with the reason in bracketscond jdbcOnClasspath false— pretend JDBC is not on the classpathrestart, thenconditionsagain — isDataSourceAutoConfigurationstill in the report at all?cond userDataSource true— simulate you defining your own DataSource, thenrestartlab autoconf filterversuslab autoconf report— how many the fast sieve removed, how many the report kept
steps 3 and 5 look similar and mean opposite things. jdbcOnClasspath false makes the configuration class vanish at the fast-sieve stage, so its name is absent from the report entirely; with userDataSource true it still appears under Negative matches, bracketed with OnBeanCondition. Those are the two testable predictions from the Section 2 double-gate picture.
Section 2 gave you the sequence in prose. The demo below turns it into something you can advance stage by stage — the autoconf scenario simulates the auto-configuration chain, and its five buttons map onto "list → filter → sort → apply → report":
- Where candidates come from (imports): sweep
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsacross every jar on the classpath and merge the fully-qualified names into one big list. This stage only does IO and checks no condition, so the count looks alarming (the built-in list alone has 130+ entries) - Condition filtering (filter):
AutoConfigurationImportFiltertakes a rough cut first. You watch a large batch of configuration classes get eliminated because classes are missing from the classpath — this is the "fast knife" of Section 7; it never parses annotations - Sorting (sort): survivors go to
AutoConfigurationSorter: resolvebefore/afterdependencies first, then@AutoConfigureOrder, then alphabetical fallback. The order printed here is the order you will later read in the report - Getting applied (apply): full condition evaluation per class; passers become
BeanDefinitions, failures are written into the report - The report (report): results rendered as Positive matches / Negative matches / Exclusions / Unconditional — byte for byte what
--debugshows in your console
the candidate count collapses hardest at filter, but what actually decides "does this bean exist" is apply. That gap between the two numbers is the answer to "I added the dependency and it still does nothing" — dependencies change the classpath, and the classpath drives condition evaluation.
Every stage of that line can be overridden by hand. These three demos each train one thing, and the last one is the practical version of Section 10's decision.
The first drills condition annotations. In cond, the missing argument demonstrates the umbrella rule — the shop lends an umbrella only because the guest brought none (no bean of that type exists, so auto-configuration supplies one); switch to report to see each verdict in the same wording --debug uses. Walk all four conditions once and you will never need to guess "why isn't it active" again:
The second runs the pipeline backwards: write a starter yourself and traverse the whole chain end to end. starter's four arguments are meta (register the auto-configuration class in the list), props (bind prefixed properties with @ConfigurationProperties), bean (create it once conditions pass), off (disable via spring.xxx.enabled=false). After this, Section 8's "step aside" mechanism stops being abstract — it is something you wrote in four lines:
The third trains "when several places declare the same property, who wins". Auto-configuration leans heavily on properties to open and close features, so precedence is mandatory knowledge: command line > JNDI > system properties > application-{profile}.yml > application.yml > defaults. The prop scenario demonstrates the overriding live:
The fourth experiment stitches this article back onto the previous one with a single picture: there are four roads an object can take into the container, and beanin's auto mode is the one this article is about — no scanning involved, no package name consulted, only the list file inside the jar. Flip to scan right afterwards and you have seen both fates side by side, which is exactly what the contrast figure below is trying to say:
And keep this contrast picture in mind — last article's "bean not found" and this article's "auto-configuration not taking effect" are two faces of one coin: your classes enter through the scanning door, framework classes through the registration door. Use the wrong door and they simply never arrive:

By now you actually hold six wrenches, but they do not grip the same bolt: some delete candidates, some change a verdict, one only matters after registration is finished. Pair each wrench with the stage it acts on, and the next outage stops being a guessing game:
The --debug report is long, but only four cells matter. Watch this animation first — it shows how the report gets written, which is exactly why we call it a decision snapshot rather than a log — then switch scenarios on the left and let the right tell you which cell to read:

Negative matches:DataSourceAutoConfiguration:- @ConditionalOnClass did not find required class 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)#go to Negative matches: the OnClassCondition in brackets already names the missing class
the reading order of the four cells matters. First confirm whether it is in Positive matches (yes → go inspect your properties; no → continue), then read Negative matches for the reason (the Condition class in brackets is the root cause), and finally check Exclusions to be sure you did not accidentally switch it off yourself. This order eliminates most "let me just tweak something and see".
Every fragment below pastes straight into a search box:
| Error fragment | Real cause | 30-second fix | Dig deeper in |
|---|---|---|---|
java.lang.NoClassDefFoundError: org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType | A class required by an auto-configuration's @ConditionalOnClass is not on the classpath — one dependency short | Run --debug and search this class name in Negative matches; it tells you which auto-configuration got skipped, then add the owning artifact | This article, Sections 7 and 13 |
ClassNotFoundException: com.rabbitmq.client.Channel while RabbitAutoConfiguration stays completely silent | Same mechanism: class missing, condition unmet, whole config class skipped. Boot never errors for a feature you did not enable | That is exactly the - @ConditionalOnClass did not find required class 'com.rabbitmq.client.Channel' (OnClassCondition) line in the report; add spring-boot-starter-amqp | This article, Section 6 |
| A custom starter silently stops working after upgrading to Spring Boot 3, with no error at all | It still registers auto-configuration under META-INF/spring.factories, whose auto-configuration entry point was removed in 3.0 | Move the class names, one per line, into META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports; do not delete spring.factories wholesale — it still serves listeners and initializers | This article, Section 3 |
Field dataSource in ... required a bean of type 'javax.sql.DataSource' that could not be found | The data-source auto-configuration was skipped or excluded, or spring.datasource.url is absent so a condition fails | Check Exclusions first, then the reason in Negative matches; if it appears in neither, the starter was never added | Article #17 Section 4 · Article #28 |
Parameter 0 of method jpaVendorAdapter in ...HibernateJpaAutoConfiguration required a bean of type '...' that could not be found | Ordering between auto-configuration classes went wrong: a bean that should register first had not arrived yet | Declare the relation with @AutoConfiguration(before/after) instead of tuning magic numbers in @AutoConfigureOrder | This article, Section 5 |
The bean 'objectMapper', defined in class path resource [...] could not be registered. A bean with that name has already been defined [...] and overriding is disabled. | Same-name bean clash, and overriding has been disabled by default since Boot 2.1 | Do not reach for allow-bean-definition-overriding; rename yours or delete the definition and let @ConditionalOnMissingBean yield | This article, Section 8 · quiz two below |
spring.autoconfigure.exclude lists a class but startup reports No auto-configuration classes found | The class name in the property is misspelled, or that class is not on the classpath at all (starter missing) | Paste the fully-qualified name from your IDE rather than typing it, and confirm the corresponding starter is in the pom | Article #17 Section 6 |
auto-configuration failing to take effect almost never throws — it simply stays absent from the container in silence. So the first move is always --debug, not editing code and hoping. Absence is not an error; absence means a condition was unmet.
That rule has one common exception: auto-configuration did apply, but it could not produce a usable bean. Then it does throw, and it puts its own authorship into the stack. Below is what a fresh project with spring-boot-starter-data-jpa and no application.yml looks like — do not read the analysis, just click the frame you think is guilty:
A new project with Data JPA and H2 ticked, application.yml left completely empty. Startup aborts with 'Failed to configure a DataSource' in the middle of the screen.
Goal: turn on --debug, read one real auto-configuration chain, and locate the origin of five beans inside the report.
pom.xml:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>autolab</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- H2 triggers DataSourceAutoConfiguration on its own; no datasource config needed --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>The main class (src/main/java/com/example/autolab/AutolabApplication.java, note the root package):
package com.example.autolab;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class AutolabApplication { public static void main(String[] args) { SpringApplication.run(AutolabApplication.class, args); }}One line is enough in src/main/resources/application.properties:
debug=trueRun mvn spring-boot:run. Expected key fragment of the startup log (ordering and exact entries vary slightly with dependencies):
:: Spring Boot :: (v3.2.5)============================CONDITIONS EVALUATION REPORT============================Positive matches:----------------- DataSourceAutoConfiguration matched: - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition) - @ConditionalOnMissingBean (types: io.r2dbc.spi.ConnectionFactory) did not find any beans (OnBeanCondition) DataSourceAutoConfiguration$EmbeddedDataSourceConfiguration matched: - @ConditionalOnMissingBean (types: javax.sql.DataSource,javax.sql.XADataSource) did not find any beans (OnBeanCondition) DispatcherServletAutoConfiguration matched: - @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition) - found 'session' scope (OnWebApplicationCondition)Negative matches:----------------- RabbitAutoConfiguration: - @ConditionalOnClass did not find required classes 'org.springframework.amqp.rabbit.core.RabbitTemplate', 'com.rabbitmq.client.Channel' (OnClassCondition) QuartzAutoConfiguration: - @ConditionalOnClass did not find required class 'org.quartz.Scheduler' (OnClassCondition)Exclusions:----------- None.Unconditional:-------------- None.... Tomcat started on port 8080 (http) with context path ''... Started AutolabApplication in 2.1 secondsNow play "find things" three times, quoting the exact line each time:
- Search
DispatcherServletAutoConfigurationin Positive matches and say who handed you theDispatcherServletbean - Pick any three entries in Negative matches, copy the Condition class name from the brackets, and say which class is missing in each
- Remove
debug=trueand start with the--debugargument instead; confirm the report is identical (and feel why Section 13 says the three enabling styles are interchangeable)
Checklist: ① you can explain Positive versus Negative matches without notes; ② for at least one negative entry you can state "adding which dependency would promote it"; ③ you can say why the line Exclusions: None. deserves a deliberate glance.
Each variant changes one thing:
- Promote a negative entry: add
spring-boot-starter-amqpand restart. You will observeRabbitAutoConfigurationmoving from Negative matches into Positive matches — the most direct proof that conditions are driven by the classpath. - Watch the yielding mechanism live: create
MyJsonConfigwith a@Bean ObjectMapper(set any date format), then hit an endpoint returning an object. You will observe the@ConditionalOnMissingBeanverdict forJacksonAutoConfigurationflip, and your formatting winning. That is option B of Section 10 executed for real. - Manufacture the bad pattern deliberately: on top of step 2, set
spring.main.allow-bean-definition-overriding=trueand name your beanobjectMapper. You will observe the conflict warning vanish while which implementation lives depends on registration order — keep both logs side by side in your notes and you will never forget why this is wrong. - Silence one auto-configuration by property: put
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfigurationinapplication.properties. You will observe it listed under Exclusions, andEmbeddedDataSourceConfigurationdisappearing from Positive matches with it.
Build a minimal starter named greeting-spring-boot-starter and walk the entire chain of this article.
- Two Maven modules:
greeting-spring-boot-starter(dependency aggregation only) andgreeting-spring-boot-autoconfigure(the code) - The auto-configuration class must carry
@AutoConfiguration,@ConditionalOnClass(GreetingService.class)and@ConditionalOnMissingBean, plus@EnableConfigurationProperties(GreetingProperties.class)binding the prefixacme.greeting GreetingPropertieshas at least two fields:prefix(default[hello]) andenabled(default true); add@ConditionalOnProperty(prefix = "acme.greeting", name = "enabled", havingValue = "true", matchIfMissing = true)on the@Beanmethod- Register the class in
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports - A separate demo project depends only on the starter, sets nothing about component scanning, injects
GreetingServicedirectly and exposesGET /greet
Checklist: ① the demo starts with zero configuration and /greet returns text carrying the default prefix; ② changing acme.greeting.prefix takes effect immediately; ③ setting acme.greeting.enabled=false makes startup fail with "required a bean of type GreetingService", and the Negative matches section of --debug names the property condition that blocked it; ④ remove the optional dependency and watch @ConditionalOnClass erase the whole configuration class; ⑤ your README draws the four stages "list → filter → sort → apply" and marks which stage each of your files participates in.
from memory, name the four stages of the auto-configuration pipeline and roughly what each does.
what separates AutoConfigurationImportFilter from ConditionEvaluator? Why call the first a fast knife and the second a fine sieve?
at which version did spring.factories give way to AutoConfiguration.imports? Which kind of starter silently dies on the jump to 3.0?
which three layers decide auto-configuration ordering? Why do the docs prefer before/after over @AutoConfigureOrder?
what is the correct way to replace an auto-configured bean? Explain it twice — once with the finished-apartment analogy, once with the umbrella analogy.
Mantra: **the list sets the candidates, conditions decide life and death, ordering decides sequence, deferral decides who yields** — one line for the whole pipeline.
the full auto-configuration pipeline is @EnableAutoConfiguration → AutoConfigurationImportSelector (deferred import) → read the AutoConfiguration.imports list → fast filter + condition evaluation → register BeanDefinition → refresh() instantiation. The list format moved from spring.factories to AutoConfiguration.imports in 3.0; ordering is decided in three layers by before/after, @AutoConfigureOrder, and alphabetical fallback; whether a class takes effect depends entirely on conditions. Two practical rules to remember: when troubleshooting auto-configuration, start with --debug and read the condition report, and override auto-configuration by defining your own bean and letting @ConditionalOnMissingBean step aside, not by turning on the overriding switch.