Spring BootSpring Boot Auto Configuration
Spring Boot

Spring Boot Auto Configuration

Auto-configuration is the engine behind Spring Boot's 'just works' experience. It examines your classpath, existing beans, and properties, then automatically configures hundreds of Spring beans without a single line of configuration from you. Understanding how it works — and how to override, extend, and debug it — turns Spring Boot from magic into a tool you fully control.

What Auto-Configuration Does

When you start a Spring Boot application, before any of your beans are created, Spring Boot runs through a list of ~150 auto-configuration classes. Each one is a @Configuration class that conditionally creates beans based on what's available in the environment. The key word is conditionally. Auto-configuration never fights with your code. Every auto-configured bean carries a @ConditionalOnMissingBean annotation — if you've already defined a bean of that type, the auto-configuration skips it entirely. This is the "opinionated but not dictatorial" design: Spring Boot provides sensible defaults that step aside the moment you make an explicit choice. The result: add spring-boot-starter-data-jpa to your dependencies, set three properties, and you have a fully configured JPA stack — DataSource, connection pool, EntityManagerFactory, TransactionManager, and Spring Data repositories — without writing a single @Bean method.

How Auto-Configuration Is Triggered

Auto-configuration starts with @EnableAutoConfiguration — one of the three annotations inside @SpringBootApplication. It imports AutoConfigurationImportSelector, which reads a file listing every auto-configuration class available on the classpath.
Java
// @SpringBootApplication expands to:
@Configuration
@EnableAutoConfiguration   // triggers auto-configuration
@ComponentScan
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// @EnableAutoConfiguration imports AutoConfigurationImportSelector
// which reads this file from every JAR on the classpath:
// META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

// Example entries in that file (from spring-boot-autoconfigure.jar):
// org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
// org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
// org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
// org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
// org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
// org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
// ... ~150 more

// Each listed class is a @Configuration class with @Conditional checks.
// Spring Boot loads ALL of them, but only APPLIES those whose
// conditions are satisfied by the current environment.

The @Conditional Annotations

Each auto-configuration class uses @Conditional annotations to decide whether it should apply. These are the precise conditions Spring Boot evaluates before creating any auto-configured bean.
Java
// @ConditionalOnClass — applies ONLY if a class is present on the classpath:
@ConditionalOnClass(DataSource.class)

// @ConditionalOnMissingClass — applies ONLY if a class is NOT on the classpath:
@ConditionalOnMissingClass("com.example.CustomDataSource")

// @ConditionalOnBean — applies ONLY if a specific bean already exists:
@ConditionalOnBean(DataSource.class)

// @ConditionalOnMissingBean — applies ONLY if a bean does NOT exist yet:
// This is what allows YOUR bean definition to override auto-configuration
@ConditionalOnMissingBean(DataSource.class)

// @ConditionalOnProperty — applies based on a property value:
@ConditionalOnProperty(
    prefix = "spring.datasource",
    name = "url"
)

// Also supports havingValue and matchIfMissing:
@ConditionalOnProperty(
    name = "app.cache.enabled",
    havingValue = "true",
    matchIfMissing = false
)

// @ConditionalOnWebApplication — applies in servlet web apps only:
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)

// @ConditionalOnNotWebApplication — applies in non-web apps only:
@ConditionalOnNotWebApplication

// @ConditionalOnExpression — applies based on a SpEL expression:
@ConditionalOnExpression("${app.feature.enabled:false} && '${app.env}' != 'test'")

// @ConditionalOnSingleCandidate — applies if exactly one bean of the type exists:
@ConditionalOnSingleCandidate(DataSource.class)

// @ConditionalOnResource — applies if a resource file exists on the classpath:
@ConditionalOnResource(resources = "classpath:myconfig.xml")

A Real Auto-Configuration Class — Dissected

Here's what a real Spring Boot auto-configuration class looks like internally. This is a simplified version of DataSourceAutoConfiguration — the class that automatically creates your database connection pool.
Java
// Simplified DataSourceAutoConfiguration (from spring-boot-autoconfigure):

@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    // Inner class for embedded databases (H2, HSQL, Derby):
    @Configuration(proxyBeanMethods = false)
    @Conditional(EmbeddedDatabaseCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import(EmbeddedDataSourceConfiguration.class)
    protected static class EmbeddedDatabaseConfiguration { }

    // Inner class for pooled databases (MySQL, PostgreSQL, etc.):
    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    protected static class PooledDataSourceConfiguration {

        @Bean
        @ConditionalOnMissingBean(JdbcConnectionDetails.class)
        PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
            return new PropertiesJdbcConnectionDetails(properties);
        }
    }
}

// DataSourceConfiguration.Hikari — how HikariCP is auto-configured:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)     // only if HikariCP is on classpath
@ConditionalOnMissingBean(DataSource.class)     // only if no DataSource defined yet
@ConditionalOnProperty(
    name = "spring.datasource.type",
    havingValue = "com.zaxxer.hikari.HikariDataSource",
    matchIfMissing = true                        // default when type not set
)
static class Hikari {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    HikariDataSource dataSource(JdbcConnectionDetails connectionDetails) {
        HikariDataSource ds = createDataSource(connectionDetails, HikariDataSource.class);
        if (StringUtils.hasLength(connectionDetails.getDriverClassName())) {
            ds.setDriverClassName(connectionDetails.getDriverClassName());
        }
        return ds;
    }
}
// Every spring.datasource.hikari.* property maps to a HikariDataSource setter

Auto-Configuration Ordering

Auto-configuration classes can declare ordering relationships — some must run before others to ensure dependencies between auto-configured beans are satisfied correctly.
Java
// @AutoConfiguration has before and after attributes for ordering:

@AutoConfiguration(before = WebMvcAutoConfiguration.class)
public class MyWebFilterAutoConfiguration { }
// Ensures this auto-config runs BEFORE WebMvcAutoConfiguration

@AutoConfiguration(after = DataSourceAutoConfiguration.class)
public class JpaRepositoriesAutoConfiguration { }
// JPA repos need the DataSource to be configured first

// @AutoConfigureBefore and @AutoConfigureAfter (older style — still valid):
@AutoConfigureBefore(SecurityAutoConfiguration.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
                      JdbcTemplateAutoConfiguration.class })
public class MySecurityAutoConfiguration { }

// @AutoConfigureOrder for numeric ordering:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class EarlyAutoConfiguration { }

// The full ordering chain for a JPA web app (simplified):
// 1. PropertyPlaceholderAutoConfiguration  — load properties
// 2. DataSourceAutoConfiguration           — create DataSource
// 3. DataSourceTransactionManagerAutoConfiguration — transactions
// 4. HibernateJpaAutoConfiguration         — EntityManagerFactory
// 5. JpaRepositoriesAutoConfiguration      — Spring Data repos
// 6. WebMvcAutoConfiguration               — DispatcherServlet, MVC config
// 7. SecurityAutoConfiguration             — Spring Security filter chain
// 8. Your @Configuration classes           — your beans, using all of the above

Overriding Auto-Configuration

Overriding auto-configuration is the most common customization pattern. Because every auto-configured bean uses @ConditionalOnMissingBean, defining your own bean of the same type completely replaces the auto-configured one.
Java
// Example 1 — Override the auto-configured DataSource:
@Configuration
public class DataSourceConfig {

    @Bean
    @Primary
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        config.setUsername("root");
        config.setPassword("secret");
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        config.setConnectionTimeout(30_000);
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        return new HikariDataSource(config);
    }
}
// Spring Boot's DataSourceAutoConfiguration sees your DataSource bean
// and @ConditionalOnMissingBean causes it to skip entirely

// Example 2 — Override the auto-configured Jackson ObjectMapper:
@Configuration
public class JacksonConfig {

    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        return JsonMapper.builder()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
            .configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true)
            .addModule(new JavaTimeModule())
            .addModule(new Jdk8Module())
            .build();
    }
}

// Example 3 — Extend Spring MVC auto-configuration without replacing it:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://myapp.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowCredentials(true);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestLoggingInterceptor());
    }
}
// WebMvcAutoConfiguration detects WebMvcConfigurer and applies your additions
// while keeping all its own defaults — no need to replace the entire auto-config

Excluding Auto-Configuration

Sometimes you want to completely prevent a specific auto-configuration from running — disable security in a test, prevent DataSource creation in a module that has no database, or remove an unwanted default behavior entirely.
Java
// Method 1 — Exclude in @SpringBootApplication:
@SpringBootApplication(exclude = {
    SecurityAutoConfiguration.class,
    UserDetailsServiceAutoConfiguration.class
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// Method 2 — Exclude in @EnableAutoConfiguration:
@Configuration
@EnableAutoConfiguration(exclude = {
    DataSourceAutoConfiguration.class,
    DataSourceTransactionManagerAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class
})
public class NoDbConfig { }

// Method 3 — Exclude via application.properties (no recompile needed):
// spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration

// Method 4 — Exclude in a specific test:
@SpringBootTest
@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
class ServiceTest { }

// Method 5 — Exclude via test profile:
// src/test/resources/application-test.properties:
// spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration

@SpringBootTest
@ActiveProfiles("test")
class AnotherServiceTest { }

Debugging Auto-Configuration

When auto-configuration behaves unexpectedly — a bean isn't being created, a configuration isn't taking effect, or something is configured you didn't expect — Spring Boot provides detailed diagnostic output.
Java
// Enable the auto-configuration conditions report:

// Option 1 — application.properties:
// debug=true

// Option 2 — command line flag:
// java -jar app.jar --debug

// Option 3 — environment variable:
// DEBUG=true java -jar app.jar

// The report prints three sections on startup:

// POSITIVE MATCHES (auto-configs that WERE applied):
// DataSourceAutoConfiguration matched:
//   - @ConditionalOnClass found required class 'javax.sql.DataSource'
//   - @ConditionalOnMissingBean did not find any DataSource beans

// NEGATIVE MATCHES (auto-configs that were SKIPPED):
// MongoAutoConfiguration did not match:
//   - @ConditionalOnClass did not find required class 'com.mongodb.client.MongoClient'
//
// RabbitAutoConfiguration did not match:
//   - @ConditionalOnClass did not find required class 'com.rabbitmq.client.Channel'

// EXCLUSIONS:
// SecurityAutoConfiguration — excluded by user

// Programmatic inspection — list every bean in the context:
@Component
public class AutoConfigInspector implements CommandLineRunner {

    @Autowired
    private ApplicationContext context;

    @Override
    public void run(String... args) {
        String[] beans = context.getBeanDefinitionNames();
        Arrays.sort(beans);
        for (String bean : beans) {
            System.out.println(bean);
        }
        System.out.println("Total beans: " + beans.length);
    }
}

// Actuator endpoints (add spring-boot-starter-actuator):
// GET /actuator/beans        — all beans with types and dependencies
// GET /actuator/conditions   — full conditions report (same as debug=true)
// GET /actuator/env          — all properties and which source they came from

Writing Your Own Auto-Configuration

You can write auto-configuration classes for your own libraries or shared internal modules. This is the same mechanism Spring Boot uses internally — your auto-configuration works identically to any built-in one.
Java
// Step 1 — Define a properties class:
@ConfigurationProperties(prefix = "app.audit")
public class AuditProperties {
    private boolean enabled = true;
    private int retentionDays = 90;
    private String tableName = "audit_log";
    // getters and setters
}

// Step 2 — Write the auto-configuration class:
@AutoConfiguration
@ConditionalOnClass(JdbcTemplate.class)
@ConditionalOnProperty(
    prefix = "app.audit",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true
)
@EnableConfigurationProperties(AuditProperties.class)
public class AuditAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public AuditService auditService(JdbcTemplate jdbcTemplate,
                                     AuditProperties properties) {
        return new JdbcAuditService(
            jdbcTemplate,
            properties.getTableName(),
            properties.getRetentionDays()
        );
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnBean(AuditService.class)
    public AuditAspect auditAspect(AuditService auditService) {
        return new AuditAspect(auditService);
    }
}

// Step 3 — Register the auto-configuration class:
// Create file: src/main/resources/META-INF/spring/
//   org.springframework.boot.autoconfigure.AutoConfiguration.imports
// Add one line: com.example.audit.AuditAutoConfiguration

// Step 4 — Test it applies correctly:
@SpringBootTest
class AuditAutoConfigurationTest {

    @Autowired(required = false)
    private AuditService auditService;

    @Test
    void auditServiceIsAutoConfigured() {
        assertThat(auditService).isNotNull();
    }
}

// Step 5 — Test that a user-defined bean overrides auto-config:
@SpringBootTest
class AuditAutoConfigOverrideTest {

    @TestConfiguration
    static class CustomAuditConfig {
        @Bean
        public AuditService auditService() {
            return new NoOpAuditService();
        }
    }

    @Autowired
    private AuditService auditService;

    @Test
    void customBeanOverridesAutoConfig() {
        assertThat(auditService).isInstanceOf(NoOpAuditService.class);
    }
}

Adding the Configuration Processor for IDE Support

Adding spring-boot-configuration-processor to your project generates metadata that enables IDE auto-completion for your custom @ConfigurationProperties classes — the same hints you get when typing spring.datasource.* in application.properties.
Java
<!-- Add to pom.xml — generates META-INF/spring-configuration-metadata.json at compile time: -->
<!--
    dependency:
      groupId: org.springframework.boot
      artifactId: spring-boot-configuration-processor
      optional: true

    The optional=true flag ensures it is excluded from the final JAR.
    After adding this, rebuild the project.
    Your IDE will then show hints for app.audit.retention-days,
    app.audit.table-name, app.audit.enabled, and so on.
-->

// Gradle equivalent (add to build.gradle):
// annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

// After adding the processor, properties show hints in application.properties:
// app.audit.enabled=true
// app.audit.retention-days=30
// app.audit.table-name=my_audit_log

@ConditionalOnProperty — In Depth

@ConditionalOnProperty is the most frequently used conditional in application-level configuration. It enables feature-flag style behavior where functionality can be toggled on and off via properties without changing code.
Java
// Basic — bean created only when property equals "true":
@Configuration
@ConditionalOnProperty(name = "app.feature.metrics", havingValue = "true")
public class MetricsConfiguration {
    @Bean
    public MetricsCollector metricsCollector() {
        return new PrometheusMetricsCollector();
    }
}
// app.feature.metrics=true  → bean created
// app.feature.metrics=false → bean NOT created
// property absent           → bean NOT created (matchIfMissing defaults to false)

// matchIfMissing=true — opt-out behavior (enabled unless explicitly disabled):
@Configuration
@ConditionalOnProperty(
    name = "app.feature.caching",
    havingValue = "true",
    matchIfMissing = true
)
public class CachingConfiguration { }
// property absent           → bean CREATED
// app.feature.caching=true  → bean CREATED
// app.feature.caching=false → bean NOT created

// Using prefix to separate namespace from property name:
@ConditionalOnProperty(prefix = "app.mail", name = "enabled")
// checks property: app.mail.enabled

// Multiple properties — ALL must match:
@ConditionalOnProperty(
    name = { "app.feature.analytics", "app.feature.tracking" },
    havingValue = "true"
)
public class FullAnalyticsConfig { }

// Combined with @Profile:
@Configuration
@Profile("prod")
@ConditionalOnProperty(name = "app.payments.provider", havingValue = "stripe")
public class StripePaymentConfig {

    @Bean
    public PaymentGateway stripeGateway(@Value("${stripe.api.key}") String apiKey) {
        return new StripePaymentGateway(apiKey);
    }
}
// Only active in prod profile AND when app.payments.provider=stripe

// Feature flag pattern — toggle entire feature sets:
@Configuration
@ConditionalOnProperty(name = "app.notifications.enabled", havingValue = "true", matchIfMissing = true)
public class NotificationConfig {

    @Bean
    public EmailNotificationService emailNotificationService() {
        return new EmailNotificationService();
    }

    @Bean
    public SmsNotificationService smsNotificationService() {
        return new SmsNotificationService();
    }
}