Spring Boot Auto-Configuration Under the Hood: @EnableAutoConfiguration and Conditional Annotations
Deep-dive into Spring Boot auto-configuration loading mechanisms, META-INF imports, and @Conditional Evaluation
Part 9 in Series — Catch up on the previous article: Why Spring Boot Exists: Eliminating XML Configuration and Dependency Hell (Part 8) before diving into this post.
Why You Need This in Real Life
Adding spring-boot-starter-data-jpa and postgresql to a Maven pom.xml provides immediate database access. You write an @Entity class and a @Repository interface, set spring.datasource.url in application.properties, and start the app. Without defining a HikariDataSource, LocalContainerEntityManagerFactoryBean, or JpaTransactionManager, your application connects to PostgreSQL, creates database tables, and handles transactions.
However, three months later, you add a second custom DataSource bean to support a legacy MySQL read replica:
@Configuration
public class LegacyDataSourceConfig {
@Bean
public DataSource mysqlDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://replica.internal:3306/legacy")
.build();
}
}
Suddenly, your main PostgreSQL repository fails with NoUniqueBeanDefinitionException or starts targeting MySQL instead of PostgreSQL.
What happened? Spring Boot’s auto-configured DataSource bean vanished. Understanding auto-configuration mechanics—specifically @ConditionalOnMissingBean—is the difference between spending hours debugging missing beans and understanding how Spring Boot dynamically inspects bean definitions at boot time.
The Root Annotation: @SpringBootApplication
Every Spring Boot entry point features @SpringBootApplication:
@SpringBootApplication
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
@SpringBootApplication is a meta-annotation composed of three critical annotations:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication { ... }
@SpringBootConfiguration: Marks the class as a configuration source (a specialized@Configuration).@ComponentScan: Tells Spring to scan the current package and sub-packages for@Component,@Service, and@Repositoryclasses.@EnableAutoConfiguration: Triggers Spring Boot’s auto-configuration discovery mechanism.
How Auto-Configuration Loads Configurations
When @EnableAutoConfiguration is evaluated, Spring Boot imports AutoConfigurationImportSelector.
+-------------------------------------------------------------------------+
| Auto-Configuration Discovery Pipeline |
| |
| 1. @SpringBootApplication |
| | |
| v |
| 2. @EnableAutoConfiguration |
| | |
| v |
| 3. AutoConfigurationImportSelector.selectImports() |
| | |
| v |
| 4. Read Manifest Files from Classpath JARs |
| - Spring Boot 2.x: META-INF/spring.factories |
| - Spring Boot 3.x: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
| | |
| v |
| 5. Filter Candidate Auto-Configuration Classes |
| - Evaluate @ConditionalOnClass, @ConditionalOnMissingBean, etc. |
| | |
| v |
| 6. Register Passing Configurations into ApplicationContext |
+-------------------------------------------------------------------------+
The Manifest Files: spring.factories vs AutoConfiguration.imports
-
Spring Boot 2.x:
AutoConfigurationImportSelectorreadsMETA-INF/spring.factoriesusingSpringFactoriesLoader. The file contains key-value pairs listing configuration classes:org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration -
Spring Boot 3.x: Simplified to
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, containing a plain line-delimited list of fully qualified class names:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
Conditional Bean Evaluation (@Conditional)
Loading 150+ auto-configuration classes on every application startup would be slow and wasteful. Spring Boot uses conditional evaluation built on top of Spring Framework’s @Conditional(Condition.class) API.
Core Conditional Annotations
1. @ConditionalOnClass & @ConditionalOnMissingClass
Evaluates whether a specific class exists on the application classpath using reflection (Class.forName()) without loading the class into memory if it’s absent.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
public class EmbeddedDataSourceConfiguration { ... }
2. @ConditionalOnMissingBean & @ConditionalOnBean
Evaluates whether a bean of a specified type already exists in the ApplicationContext. This enables developer overrides: if you define your own custom DataSource bean, Spring Boot’s auto-configuration backs off.
@Bean
@ConditionalOnMissingBean(type = "javax.sql.DataSource")
public HikariDataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
3. @ConditionalOnProperty
Checks if a specific environment property is present and matches a expected value.
@Bean
@ConditionalOnProperty(name = "feature.payment.v2.enabled", havingValue = "true")
public PaymentV2Service paymentV2Service() {
return new PaymentV2Service();
}
Writing a Custom Auto-Configuration
To understand how auto-configuration works end-to-end, let’s create a custom audit logger starter library.
Step 1: Create the Configuration Class
package com.example.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(name = "audit.logger.enabled", havingValue = "true", matchIfMissing = true)
public class AuditLoggerAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AuditLogger auditLogger() {
return new ConsoleAuditLogger();
}
}
Step 2: Register in AutoConfiguration.imports
Create src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.example.autoconfigure.AuditLoggerAutoConfiguration
When an external application includes your JAR on its classpath, Spring Boot discovers AuditLoggerAutoConfiguration, checks if audit.logger.enabled is true, verifies no other AuditLogger bean exists, and automatically creates ConsoleAuditLogger.
Diagnostic Tools: Debugging Auto-Configuration
When beans fail to load or unexpected beans are created, run your application with the --debug flag or set logging.level.org.springframework.boot.autoconfigure=DEBUG.
Spring Boot outputs an Auto-Configuration Report:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)
Negative matches:
-----------------
MongoAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'com.mongodb.client.MongoClient' (OnClassCondition)
Next Steps
In the next post, we will inspect how Spring Boot Starters package dependencies and examine how embedded web servers (Tomcat/Jetty) execute directly inside executable fat JARs.
References & Further Reading
- Spring.io. Spring Boot Reference Guide — Developing Your Own Starter. Spring Docs.
- Apache Software Foundation. Apache Maven Specification — Dependency Management & Bill of Materials (BOM). Apache Maven Docs.
- Webb, P., et al. (2023). Spring Boot Documentation. VMware Tanzu.
Part 10: Spring Boot Starters & Embedded Web Servers: How Tomcat Runs Inside an Executable JAR
Continue to Part 10 →