Spring Boot Auto-Configuration: Missing HikariCP, No Error
A transitive dependency removed HikariCP; auto-config silently skipped DataSource, causing NPE.
20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.
- ✓Basic programming fundamentals
- ✓A computer with internet access
- ✓Willingness to follow along with examples
- Auto-Configuration is conditional bean wiring — Spring Boot loads config classes from JAR dependencies but only executes them if classpath and property conditions are met
- @EnableAutoConfiguration triggers a scan of META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (or spring.factories in older versions) across all JARs
- @ConditionalOnClass activates config only when a specific class exists on the classpath — this is how starters adapt to your dependencies
- @ConditionalOnMissingBean provides a default 'opinion' but steps aside when you define your own bean — this is the override mechanism
- The Conditions Evaluation Report (debug=true) shows every positive and negative match with the exact reason each config was accepted or rejected
- The biggest mistake: defining a bean that conflicts with auto-config without understanding @ConditionalOnMissingBean should have skipped the default
Spring Boot auto-configuration is the mechanism that automatically configures beans based on dependencies on your classpath, eliminating the boilerplate of manual @Bean declarations for common infrastructure. When you add spring-boot-starter-data-jpa to your project, Spring Boot scans for HikariCP on the classpath and, if present, automatically creates a DataSource bean with sensible defaults—no @Configuration class required.
This isn't magic; it's a deterministic system of conditional annotations (@ConditionalOnClass, @ConditionalOnMissingBean) that evaluate at startup, producing a detailed conditions evaluation report you can view via --debug or in the /actuator/conditions endpoint. The core contract lives in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, a single file that lists all auto-configuration classes in load order, giving you complete visibility into what's being applied and why.
Auto-configuration solves the problem of framework integration complexity by shifting configuration responsibility from the developer to the classpath. Instead of writing a DataSourceConfig class that checks for HikariCP, creates connection pools, and sets timeouts, you let Spring Boot's DataSourceAutoConfiguration handle it—but only if you haven't already defined your own DataSource bean (the @ConditionalOnMissingBean guard).
This pattern means your custom auto-configuration library can safely provide defaults without overriding user choices. The trade-off is that when things go wrong—like a missing HikariCP jar producing no error but a broken connection pool—you need to understand the conditions report to debug why a bean wasn't created.
Disabling auto-configuration is straightforward: spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration in application.properties gives you full control when the defaults don't fit.
In practice, auto-configuration is the backbone of every Spring Boot starter, from spring-boot-starter-web (which auto-configures DispatcherServlet and embedded Tomcat) to spring-boot-starter-security (which sets up the security filter chain). The alternative is manual configuration with @EnableAutoConfiguration or @Import directives, which you'd use only when building a non-Spring-Boot application or when you need to exclude specific auto-configurations for performance reasons.
The power lies in the conditional guards: your custom library's auto-configuration should always use @ConditionalOnClass to check for required dependencies and @ConditionalOnMissingBean to respect user overrides, preventing your library from breaking every application that includes it. This is why the AutoConfiguration.imports file is the single most important file in any Spring Boot starter—it's the registry that determines which configurations are candidates for activation, and getting its ordering wrong can cascade into runtime failures that are notoriously hard to debug.
Imagine you buy a high-end smart home kit. In the old days — Standard Spring — you had to manually wire the doorbell to the speaker, the lights to the switch, and the thermostat to the heater using a thick manual nobody wanted to read. With Spring Boot Auto-Configuration, the system is smart in the way a good contractor is smart: it walks into the room, sees what is already there, and makes sensible connections without asking you to approve each wire.
It looks around and says: 'I see a lightbulb and a switch — I will connect them.' If you have already installed your own professional dimmer switch — a custom bean — the system notices and steps back: 'You have this covered, I will leave yours in place.' That is not magic. That is conditional logic that respects your decisions. Understanding this distinction is what separates engineers who fight Spring Boot from engineers who ship with it.
Auto-Configuration is the mechanism that allows Spring Boot to achieve its 'just run it' experience. While critics call it magic, it is a predictable sequence of conditional logic gates that evaluate your classpath, your existing beans, and your application properties at startup — in that order.
Misunderstanding auto-configuration causes real production failures. A missing classpath dependency silently skips a critical config class and you find out at 2 AM when every database call is throwing NullPointerException. A conflicting user-defined bean triggers NoUniqueBeanDefinitionException in an environment you cannot reproduce locally. A slow startup caused by evaluating hundreds of unnecessary conditions costs 30 seconds per deployment multiplied across 50 Kubernetes replicas — time nobody budgeted for.
I have debugged all three of these. What they share is that the information was available the whole time in the Conditions Evaluation Report. The engineers involved just did not know to look there.
This guide deconstructs the spring-boot-autoconfigure module to show exactly how the framework manages bean creation using the @Conditional ecosystem. By the end, you will understand how to write your own auto-configurations, read the Conditions Report without panic, and debug missing bean issues without guessing.
How Spring Boot Auto-Configuration Eliminates Boilerplate Without Magic
Spring Boot auto-configuration is a conditional bean registration engine that inspects your classpath, existing beans, and property sources to automatically configure Spring beans that would otherwise require explicit @Bean definitions. The core mechanic is the @Conditional family of annotations — @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty — which gate each auto-configuration class. When HikariCP is on the classpath and no DataSource bean exists, Spring Boot registers one using application.properties values. If HikariCP is absent, the DataSourceAutoConfiguration simply skips registration, and no error is thrown unless your code explicitly requires a DataSource. This is why removing the HikariCP dependency from a Spring Boot app that never injects a DataSource produces no failure — the auto-configuration class evaluates to false and does nothing. In practice, auto-configuration is ordered via @AutoConfigureBefore, @AutoConfigureAfter, and @AutoConfigureOrder to resolve dependencies between configuration classes. The spring.factories file in spring-boot-autoconfigure lists all auto-configuration candidates, and Spring Boot evaluates them in order, applying conditions against the current ApplicationContext. You can exclude specific auto-configurations via @SpringBootApplication(exclude=...) or spring.autoconfigure.exclude. Use auto-configuration to reduce manual setup for standard infrastructure — data sources, JPA, security, messaging — but always verify what gets configured by enabling debug logging (debug=true) to see positive and negative condition matches. Over-reliance without understanding the conditions leads to silent misconfiguration in production.
The Mechanics of Auto-Configuration: Classpath Discovery
When you annotate your main class with @SpringBootApplication, you are implicitly enabling @EnableAutoConfiguration. This triggers a search for a file named META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports in Spring Boot 3.x, or META-INF/spring.factories in Spring Boot 2.x, across every JAR on your classpath.
Inside these files is a flat list of fully qualified configuration class names. Spring Boot attempts to load all of them. Here is where the conditional logic takes over: every class in that list is annotated with one or more @Conditional annotations that are evaluated before any @Bean method is executed. A configuration class only runs its @Bean methods if every condition on the class passes. If a single condition fails — classpath missing a class, property not set, required bean absent — the entire configuration class is skipped. Silently.
This is the mechanism that makes a single spring-boot-starter-data-jpa dependency configure DataSource, EntityManagerFactory, TransactionManager, and Hibernate dialect without you writing a line of configuration XML. It is also the mechanism that silently does nothing when HikariCP disappears from your resolved classpath due to a dependency conflict.
package io.thecodeforge.autoconfig; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariConfig; /** * io.thecodeforge: Production-grade conditional configuration. * * Three conditions must ALL pass for this class to execute: * 1. HikariDataSource must be on the classpath * 2. The property forge.database.enabled must be true (or absent — matchIfMissing) * 3. No DataSource bean may already exist in the context * * If any condition fails, this entire class is silently skipped. * No error. No warning. No log line at INFO or WARN level. * The Conditions Evaluation Report at debug=true is the only place this skip is recorded. */ @AutoConfiguration // Preferred over @Configuration for auto-config classes in Spring Boot 3.x @ConditionalOnClass(HikariDataSource.class) @ConditionalOnProperty( name = "forge.database.enabled", havingValue = "true", matchIfMissing = true // Activates even when property is absent — opt-out model ) public class ForgeDbAutoConfiguration { @Bean @ConditionalOnMissingBean(DataSource.class) // Class-level check — if ANY DataSource bean exists, skip this public DataSource dataSource(ForgeDataSourceProperties props) { HikariConfig config = new HikariConfig(); config.setJdbcUrl(props.getUrl()); config.setUsername(props.getUsername()); config.setPassword(props.getPassword()); config.setMaximumPoolSize(props.getMaxPoolSize()); config.setConnectionTimeout(props.getConnectionTimeoutMs()); return new HikariDataSource(config); } }
- @ConditionalOnClass checks if a specific class exists on the classpath — evaluated before the Spring context is fully initialized, making it the cheapest condition to evaluate
- @ConditionalOnMissingBean checks if you already defined a bean of that type — your bean wins unconditionally, the auto-configured default steps aside without complaint
- @ConditionalOnProperty checks if a property is set to a specific value — enables runtime toggling between implementations without code changes
- @ConditionalOnBean is the inverse of @ConditionalOnMissingBean — config only activates if a specific prerequisite bean already exists in the context
- @ConditionalOnWebApplication skips configuration entirely for non-web contexts like batch jobs or CLI runners — prevents web-only beans from polluting a non-web context
- Multiple @Conditional annotations on the same class compose with AND logic — every condition must pass, and evaluation stops at the first failure
Debugging the Magic: The Conditions Evaluation Report
The biggest source of frustration with auto-configuration is not that it fails — it is that it fails silently. A condition evaluates to false, the config class is skipped, and Spring Boot moves on without logging anything at INFO level. You are left with a missing bean and no obvious explanation.
The Conditions Evaluation Report fixes this. Add debug=true to application.properties and restart. Spring Boot will print every auto-configuration class it evaluated, categorized into Positive Matches (ran and created beans), Negative Matches (skipped and why), and Unconditional Classes (always run regardless of conditions). Each entry in Negative Matches shows the exact @Conditional annotation that failed and the value it tested against.
This report is also available at runtime without a restart via the Actuator /actuator/conditions endpoint. The JSON format is parseable, diffable between deployments, and can be integrated into your CI pipeline to detect configuration drift.
I treat the Conditions Report as the first tool, not the last resort. When a bean is missing, I check the report before I check the code.
package io.thecodeforge.debug; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import javax.sql.DataSource; /** * io.thecodeforge: Startup validation that fails fast if critical beans are absent. * * This pattern catches silent auto-configuration skips at boot time * instead of at runtime under production traffic. * * Place in the root application package so @ComponentScan picks it up. * Remove from test contexts with @Profile("!test") if needed. */ @Component public class ForgeStartupValidator implements ApplicationRunner { private final ApplicationContext context; public ForgeStartupValidator(ApplicationContext context) { this.context = context; } @Override public void run(ApplicationArguments args) { validateCriticalBean(DataSource.class, "DataSource bean is absent. Check the Conditions Evaluation Report: " + "run with debug=true or query /actuator/conditions. " + "Likely cause: HikariCP missing from resolved classpath due to dependency conflict."); } private void validateCriticalBean(Class<?> type, String failureMessage) { if (context.getBeanNamesForType(type).length == 0) { throw new IllegalStateException(failureMessage); } } }
Why You Build Custom Auto-Configuration and the One File That Controls It All
You don't write auto-configuration for fun. You write it because you own a shared library—an audit logger, a rate limiter, a security filter—that every service in your org consumes. Without auto-configuration, each team copies your bean definitions, misses one, and calls you at 2 AM.
The entry point is a file nobody talks about: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. No XML. No magic. Just a flat list of fully qualified class names, one per line. Spring Boot iterates that file at startup and processes every class you listed.
Your class must be annotated @Configuration. But if you stop there, you've just created a regular config that always loads. That's not auto-configuration, that's a global tax. Real auto-configuration only activates when conditions are met—when a driver is on the classpath, when a property is set, when a bean is missing. That's what @Conditional family is for.
// io.thecodeforge — java tutorial package io.thecodeforge.audit; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @AutoConfiguration @ConditionalOnClass(name = "io.thecodeforge.audit.AuditClient") public class AuditAutoConfiguration { @Bean @ConditionalOnMissingBean public AuditService auditService() { return new AuditService(); } } // AutoConfiguration.imports content: // io.thecodeforge.audit.AuditAutoConfiguration
AutoConfiguration.imports under META-INF/spring/. The old spring.factories path still works for most starter libraries but will break with Spring Boot 3.2+. If your auto-config doesn't load, check the file name first—it's the #1 silent failure.Conditional Guards: The Valve That Prevents Your Library from Taking Down Every App
An auto-configuration that always runs is a liability. If you register a bean that pulls in a dependency that isn't there, the app crashes at startup. That's why every auto-configuration class is wrapped in @Conditional guards.
Think of conditions as valves. @ConditionalOnClass checks if a jar is on the classpath. @ConditionalOnBean checks if some other component is already registered. @ConditionalOnProperty reads application.properties and lets the user opt out without removing the dependency. @ConditionalOnMissingBean is the most important: it says "only create this default bean if the user hasn't already defined one." That's how you give teams control—they override your default by writing their own @Bean.
You can also write your own Condition implementation. Implement org.springframework.context.annotation.Condition, override , and reference it with matches()@Conditional(MyCustomCondition.class). Use this when you need to check environment variables, cloud metadata, or runtime flags that the built-in annotations don't cover.
// io.thecodeforge — java tutorial package io.thecodeforge.db; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.*; import org.springframework.context.annotation.Bean; import javax.sql.DataSource; @AutoConfiguration @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver") @ConditionalOnProperty(name = "io.thecodeforge.db.enabled", havingValue = "true", matchIfMissing = true) public class DatabaseAutoConfiguration { @Bean @ConditionalOnMissingBean public DataSource dataSource() { // Only invoked if MySQL driver is on classpath // AND property is not explicitly set to false // AND no other DataSource bean exists return new com.zaxxer.hikari.HikariDataSource(); } }
logging.level.org.springframework.boot.autoconfigure=DEBUG and inspect the condition evaluation report at startup. It tells you exactly which conditions matched and which didn't, with reasons. No server restart: just change the log level and rerun.Disabling Auto-Configuration: The Override Pattern Your Future Self Will Thank You For
Sometimes a library's auto-configuration is wrong for your app. Maybe you brought in spring-boot-starter-data-jpa but you're using a different ORM. Maybe the default DataSource doesn't match your cloud environment. You don't break the dependency—you disable the auto-configuration.
Two ways. First, in application.properties: spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration. Add a comma-separated list if you need multiple exclusions. This is the quick fix and the one you'll use when you're debugging a startup error at 4 PM on Friday.
Second, on the main @SpringBootApplication class: @SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class). This is compile-time safe and survives refactors. IDE-friendly. Use this for permanent exclusions.
If you own a library and want to let users disable your entire auto-configuration, expose a property like io.thecodeforge.autoconfigure.enabled=false and guard every @Bean with @ConditionalOnProperty. Nobody will hate you for giving them an off switch.
// io.thecodeforge — java tutorial package io.thecodeforge; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; @SpringBootApplication(exclude = { HibernateJpaAutoConfiguration.class, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } // Alternative: application.properties // spring.autoconfigure.exclude= // org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, // org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
application.properties silently becomes stale—no compile error, but the exclusion stops working. Always prefer the annotation-based exclude on @SpringBootApplication for permanent exclusions, and use the property only for temporary debugging.The First 100 Milliseconds: What Happens When You Boot an Auto-Configured App
Before your first @Bean ever gets created, Spring Boot already decided your datasource, your JSON mapper, and your security filter chain. You never wrote those beans. They just appeared. That's the whole point of auto-configuration — but understanding when it fires saves you from blaming the framework when things break.
At startup, Spring Boot scans META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. That file lists every auto-configuration class on the classpath. No guessing. No reflection magic. Just a flat list of candidates. Each candidate gets evaluated against the current environment — which beans already exist, which classes are on the classpath, what properties you set.
This all happens before your application context is fully wired. The framework builds a decision tree in milliseconds, then commits to a set of auto-configured beans. If a condition fails — say, DataSourceAutoConfiguration because you already defined your own — it simply skips that config. No side effects. No errors. Just silent delegation. This is why disabling auto-configuration is explicit: you are telling Spring "I already handled this, stay out."
// io.thecodeforge — java tutorial import org.springframework.boot.autoconfigure.AutoConfigurationImportEvent; import org.springframework.boot.autoconfigure.AutoConfigurationImportListener; public class AutoConfigTiming implements AutoConfigurationImportListener { @Override public void onAutoConfigurationImport(AutoConfigurationImportEvent event) { // Fires BEFORE bean creation — just the candidate list System.out.println("Candidates at startup: " + event.getCandidateConfigurations()); System.out.println("Exclusions: " + event.getExclusions()); // If you see your config here, it's being evaluated — not yet instantiated } }
@ConditionalOnClass can silently disable your entire library. Always check the Conditions Evaluation Report first — it tells you exactly which conditions passed and why.Own the Onion: How to Build a Custom Auto-Configuration That Won't Leak
Writing custom auto-configuration is a rite of passage for library authors. Get it wrong, and your users will curse your library for stealing startup time or breaking their app silently. Get it right, and you become the dependency everyone trusts.
The single file you need: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. One class per line. That's your contract. But that file is the output of your design. The real work is in the @AutoConfiguration class itself.
Structure your auto-configuration like an onion: outer layer is the public API, inner layer is the conditional guard. Always put @ConditionalOnClass for your library's main dependency — if it's not on the classpath, bail immediately. Then add @ConditionalOnProperty so users can toggle your config without removing jars. Finally, expose a clean @ConfigurationProperties class so users can override defaults without touching your beans.
Never, ever create beans unconditionally. Every bean you auto-create takes time and memory. Your auto-configuration is a guest in someone else's application; be polite. Use @AutoConfigureOrder if your config must run before or after another. And for the love of production, write an autoconfigure module separate from your library code — users who hate auto-configuration can just exclude the module entirely.
// io.thecodeforge — java tutorial package com.mylib.autoconfigure; import com.mylib.core.FeatureEngine; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @AutoConfiguration @ConditionalOnClass(FeatureEngine.class) @ConditionalOnProperty(prefix = "mylib", name = "enabled", havingValue = "true", matchIfMissing = true) @EnableConfigurationProperties(MyLibProperties.class) public class MyLibraryAutoConfiguration { @Bean @ConditionalOnMissingBean public FeatureEngine featureEngine(MyLibProperties props) { return new FeatureEngine(props.getApiKey(), props.getTimeout()); } }
Gradually Replacing Auto-configuration
Auto-configuration is a default, not a destiny. When your application outgrows Spring Boot's assumptions—perhaps you need a custom DataSource pool or a different Jackson ObjectMapper—you must replace parts without breaking everything. The principle is incremental override: start by defining a @Bean in your configuration class that matches the auto-configured bean's type and name. Spring Boot's @ConditionalOnMissingBean ensures your bean wins. For deeper control, exclude specific auto-configuration classes via spring.autoconfigure.exclude in application.properties, or use @EnableAutoConfiguration(exclude = {…}). When replacing, always test with the Conditions Evaluation Report to confirm your bean is active and the default is gone. The why is stability: replacing everything at once cascades failures. Instead, swap one bean at a time, verify each change, and only then move to the next. This gradual approach mirrors how you'd refactor legacy code—safe, traceable, and reversible.
// io.thecodeforge — java tutorial import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CustomDataSourceConfig { @Bean @ConditionalOnMissingBean public javax.sql.DataSource dataSource() { return new com.zaxxer.hikari.HikariDataSource(); } }
6. Conclusion
Spring Boot Auto-Configuration is not magic—it's a disciplined system of classpath scanning, conditional annotations, and property-driven overrides. The core advantage is speed: you skip hundreds of lines of boilerplate and start with sensible defaults. Yet the real power is control. You now know how to debug auto-configuration with the Conditions Evaluation Report, disable what you don't need, build custom auto-configurations without leaking internals, and gradually replace defaults as your application evolves. Each technique serves a single purpose: keeping your codebase maintainable as constraints change. Remember, Spring Boot auto-configuration is a scaffold, not a prison. The moment a default no longer fits, you have the tools—@ConditionalOnMissingBean, exclude filters, custom starters—to reshape it. Write simple configurations, lean on the report for diagnostics, and always favor gradual replacement. Your future self will thank you when a library upgrade doesn't break your entire app.
// io.thecodeforge — java tutorial import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
The Missing DataSource — Silent Auto-Configuration Skip
- Auto-Configuration silently skips classes when @ConditionalOnClass fails — no error, no warning, no log line at any level. The absence is the only signal.
- Always check the Conditions Evaluation Report with debug=true when a bean you expect is not present. The negative matches section shows exactly which condition failed and what value was evaluated.
- Never rely on transitive dependency resolution for runtime-critical libraries like HikariCP, Jackson, or Hibernate. Declare them explicitly with a pinned version in your build file.
- Add startup assertions for critical infrastructure beans.
ApplicationContext.getBean()inside an ApplicationRunner throws a meaningful error at boot time instead of a cryptic NullPointerException under production load. - Make the Conditions Evaluation Report part of your incident runbook. A five-second grep against startup logs resolves in minutes what otherwise takes hours.
grep 'debug=true' src/main/resources/application.properties || echo 'debug=true' >> src/main/resources/application.propertiesjava -jar app.jar 2>&1 | grep -A2 'Negative matches' | head -50curl -s http://localhost:8080/actuator/beans | jq '.contexts.*.beans | to_entries[] | select(.value.type | contains("DataSource"))'curl -s http://localhost:8080/actuator/conditions | jq '.positiveMatches | keys'curl -s http://localhost:8080/actuator/conditions | jq '.positiveMatches | length'curl -s http://localhost:8080/actuator/conditions | jq '.negativeMatches | length'curl -s http://localhost:8080/actuator/env/spring.datasource.url | jq '.property'curl -s http://localhost:8080/actuator/env | jq '.propertySources[] | select(.name | contains("applicationConfig"))'| Feature | Standard Spring (Manual) | Spring Boot (Auto-Config) |
|---|---|---|
| Setup Effort | High: Every bean must be declared explicitly in XML or @Configuration classes. Nothing is assumed about your intent. | Minimal: Starter dependencies include auto-configuration logic that activates based on what is present on the classpath. |
| Classpath Awareness | None: You must manually ensure @Bean definitions match the JARs you have added. Mismatches cause NoClassDefFoundError at runtime. | High: @ConditionalOnClass evaluates the classpath at startup and adapts configuration to match exactly what dependencies are present. |
| Overrideability | Explicit: You change the source XML or @Configuration class directly. Changes are visible in version control. No hidden behavior. | Seamless: @ConditionalOnMissingBean detects your bean at startup and steps aside. The override is invisible unless you check the Conditions Report. |
| Visibility | Explicit: All beans are declared in files you own. Reading the config tells you exactly what is in the context. | Implicit: Beans are created by framework code you did not write. Requires Actuator /actuator/beans and debug=true to see the full picture. |
| Startup Performance | Predictable: Only the beans you declared are created. No conditional evaluation overhead beyond what you wrote. | Variable: Hundreds of auto-configuration classes are evaluated at startup. Unused evaluations add overhead. Exclusions reduce this cost measurably. |
| Debugging Complexity | Low: Missing bean means you forgot to declare it. The fix is obvious — add the @Bean definition. | Higher: Missing bean could mean a failed condition, a wrong property value, a classpath conflict, or a missing registration file. Requires systematic investigation via the Conditions Report. |
| File | Command / Code | Purpose |
|---|---|---|
| io | /** | The Mechanics of Auto-Configuration |
| io | /** | Debugging the Magic |
| AuditAutoConfiguration.java | @AutoConfiguration | Why You Build Custom Auto-Configuration and the One File Tha |
| DatabaseAutoConfiguration.java | @AutoConfiguration | Conditional Guards |
| Application.java | @SpringBootApplication(exclude = { | Disabling Auto-Configuration |
| AutoConfigTiming.java | public class AutoConfigTiming implements AutoConfigurationImportListener { | The First 100 Milliseconds |
| MyLibraryAutoConfiguration.java | @AutoConfiguration | Own the Onion |
| GradualReplace.java | @Configuration | Gradually Replacing Auto-configuration |
| Application.java | @SpringBootApplication | 6. Conclusion |
Key takeaways
Common mistakes to avoid
5 patternsDefining a bean that conflicts with auto-config without understanding @ConditionalOnMissingBean
Forgetting to register custom auto-configuration in the imports file
Over-reliance on auto-configuration without auditing what is being evaluated at startup
Assuming auto-configuration always creates the bean you expect without verifying
Not understanding that @ConditionalOnClass silently skips configuration when the class is missing from the classpath
Interview Questions on This Topic
Explain the internal working of @SpringBootApplication. Which three annotations does it consolidate and what does each one actually do?
What is the difference between @ConditionalOnClass and @ConditionalOnBean? Give a concrete production scenario where you would use each.
How does Spring Boot know which auto-configuration classes to load? Walk me through the discovery mechanism from @EnableAutoConfiguration to bean creation.
ImportCandidates.load() reads every META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports file from every JAR on the classpath. In Spring Boot 2.x, SpringFactoriesLoader.loadFactoryNames() reads META-INF/spring.factories under the key org.springframework.boot.autoconfigure.EnableAutoConfiguration. (2) The result is a flat list of fully qualified class names — potentially hundreds of them from spring-boot-autoconfigure alone. (3) Spring Boot loads each class and reads its @Conditional annotations without executing any @Bean methods yet. (4) Each class's conditions are evaluated in order against the current classpath, application properties, and partial ApplicationContext state. (5) Only classes where every condition passes are instantiated and their @Bean methods are executed to register beans. Classes where any condition fails are recorded in the Conditions Evaluation Report under Negative Matches and produce no beans and no errors. This is how a single spring-boot-starter-data-jpa dependency can configure DataSource, EntityManagerFactory, TransactionManager, JpaRepositories, and Hibernate dialect — all conditional on the right classes being present, the right properties being set, and no conflicting user-defined beans existing.If I want to disable a specific auto-configuration like RabbitMQ without removing the dependency from my build, what are my options and when would you choose each?
Why should the @SpringBootApplication class be in the root package and what happens if it is not?
Frequently Asked Questions
Component scanning finds classes in your own package tree annotated with @Component, @Service, @Repository, or @Controller and registers them as beans. It is driven by the @ComponentScan annotation and is scoped to packages you control and own.
Auto-configuration finds configuration instructions provided by JAR dependencies on your classpath. It reads META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Spring Boot 3.x) or META-INF/spring.factories (Spring Boot 2.x), evaluates @Conditional annotations on each listed class, and creates beans for third-party libraries based on what conditions pass.
They run at different phases of the startup lifecycle and serve different purposes. Component scanning handles your application code. Auto-configuration handles framework and library integration. A common mistake is placing a custom auto-configuration class in your package tree and expecting component scanning to discover it — component scanning does not discover auto-configuration. The imports file is the only discovery mechanism that works.
Use Spring Boot Actuator's /actuator/conditions endpoint. It returns the same data as the startup Conditions Evaluation Report in JSON format — a positiveMatches object listing every active auto-configuration with the conditions that passed, and a negativeMatches object listing every skipped auto-configuration with the exact condition that rejected it.
To enable the endpoint, add spring-boot-starter-actuator as a dependency and expose it: management.endpoints.web.exposure.include=conditions in application.properties.
Query it with: curl -s http://localhost:8080/actuator/conditions | jq '.negativeMatches | keys'
The Actuator approach is preferred over debug=true for production environments because it does not require a restart, the output is structured JSON you can parse programmatically, and it can be queried after deployment to verify the configuration state without restarting the instance.
Yes, and it is the correct pattern for platform libraries. The steps are: (1) Create a @Configuration class (or use @AutoConfiguration in Spring Boot 3.x) with appropriate @Conditional annotations — @ConditionalOnClass for the library's presence, @ConditionalOnMissingBean on each @Bean method to allow user overrides, @ConditionalOnProperty for feature-flag style control. (2) Register the fully qualified class name in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports for Spring Boot 3.x — one class name per line, no key-value syntax. For Spring Boot 2.x, use META-INF/spring.factories under the key org.springframework.boot.autoconfigure.EnableAutoConfiguration. (3) Add the spring-boot-configuration-processor dependency at compile scope so Spring Boot generates metadata for your @ConfigurationProperties classes — this enables IDE autocomplete for your library's properties. (4) Verify the file is packaged correctly in the built JAR before publishing. (5) When users add your JAR to their classpath, Spring Boot automatically discovers, evaluates, and — if conditions pass — executes your configuration.
If both classes pass their conditions and both define a @Bean method returning the same type without @ConditionalOnMissingBean, Spring throws NoUniqueBeanDefinitionException during context initialization. This is a configuration error, not a Spring Boot bug.
The standard fix within auto-configuration code: add @ConditionalOnMissingBean to the @Bean method in at least one of the two classes. This ensures only one creates the bean — the first one to be evaluated wins, and the second detects an existing bean and skips. Spring Boot's own auto-configurations follow this pattern consistently.
If you cannot modify both classes because one is from a third-party library, use @SpringBootApplication(exclude = {ConflictingAutoConfig.class}) to remove the configuration you do not want, or add @Primary to a user-defined bean that should take priority and exclude both auto-configured ones.
Spring Boot 2.x uses META-INF/spring.factories — a properties-format file with the key org.springframework.boot.autoconfigure.EnableAutoConfiguration mapping to a comma-separated list of fully qualified class names. SpringFactoriesLoader is responsible for reading and parsing this file across all JARs.
Spring Boot 3.x uses META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports — a plain text file with one fully qualified class name per line, no key-value syntax, no commas. ImportCandidates replaces SpringFactoriesLoader for auto-configuration discovery. The file name encodes the purpose, making the format more readable and reducing the risk of key typos that caused silent discovery failures in 2.x.
Spring Boot 3.x also introduced the @AutoConfiguration annotation as a specialized alternative to @Configuration for auto-configuration classes — it communicates intent more clearly and enables additional tooling support.
The @Conditional annotation ecosystem — @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty, @ConditionalOnBean — is identical between versions. If you are migrating a library from 2.x to 3.x, the conditional logic stays the same; only the registration file format and location change. You can provide both files in the same JAR to support both versions during a transition period.
20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.
That's Spring Boot. Mark it forged?
7 min read · try the examples if you haven't