Adetayo Akinsanya unkletayo.dev

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

Adetayo Akinsanya (unkletayo) 2026-09-15

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 { ... }
  1. @SpringBootConfiguration: Marks the class as a configuration source (a specialized @Configuration).
  2. @ComponentScan: Tells Spring to scan the current package and sub-packages for @Component, @Service, and @Repository classes.
  3. @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: AutoConfigurationImportSelector reads META-INF/spring.factories using SpringFactoriesLoader. 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

  1. Spring.io. Spring Boot Reference Guide — Developing Your Own Starter. Spring Docs.
  2. Apache Software Foundation. Apache Maven Specification — Dependency Management & Bill of Materials (BOM). Apache Maven Docs.
  3. Webb, P., et al. (2023). Spring Boot Documentation. VMware Tanzu.

Up Next in Series →

Part 10: Spring Boot Starters & Embedded Web Servers: How Tomcat Runs Inside an Executable JAR

Continue to Part 10 →