Spring Boot Auto-Configuration — How It Works and How to Override It

by Arif Ikhsanudin, Backend Developer

What auto-configuration actually does

Auto-configuration is the mechanism that makes spring-boot-starter-data-jpa wire up a DataSource, EntityManagerFactory, and transaction manager without any explicit bean definitions. Add the dependency, provide the connection properties, and the infrastructure appears.

The mechanism: Spring Boot reads a list of auto-configuration classes from every JAR on the classpath. Each class carries @Conditional annotations that determine whether it applies. The conditions check for the presence of classes, existing beans, property values, and resource files. Only conditions that pass cause the auto-configuration to apply.

The list of auto-configuration classes is in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Spring Boot 2.7+, replacing spring.factories). Every Spring Boot starter ships this file — it's what makes starters self-configuring.

The @Conditional mechanism

Every auto-configuration class is annotated with one or more @Conditional annotations. The built-in conditions cover the common cases:

@ConditionalOnClass — applies only if a class is on the classpath:

@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
public class DataSourceAutoConfiguration {
    // Only applies if DataSource and EmbeddedDatabaseType are on the classpath
}

Adding spring-boot-starter-data-jpa to the classpath satisfies this condition. Without the starter, the condition fails and no database auto-configuration applies — cleanly.

@ConditionalOnMissingBean — applies only if a bean of the specified type doesn't already exist:

@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DataSourceProperties properties) {
    return properties.initializeDataSourceBuilder().build();
}

This is the override pattern: define your own DataSource bean and this auto-configured bean doesn't apply. The condition checks the bean type (DataSource), not the bean name — any DataSource bean, regardless of name, satisfies the condition.

@ConditionalOnProperty — applies only if a property has a specific value:

@ConditionalOnProperty(
    prefix = "spring.datasource",
    name = "url"
)
public DataSource dataSource(...) { ... }

@ConditionalOnWebApplication — applies only in a web application context.

@ConditionalOnMissingClass — the inverse of @ConditionalOnClass.

@ConditionalOnSingleCandidate — applies only if exactly one bean of the type exists (used for auto-configuration that wraps a uniquely-identified infrastructure bean).

Override pattern 1: define your own bean

The primary override mechanism is defining a @Bean of the same type. Auto-configuration checks for your bean first and backs off:

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(System.getenv("DATABASE_URL"));
        config.setMaximumPoolSize(20);
        config.setConnectionTimeout(30_000);
        config.setLeakDetectionThreshold(60_000);
        return new HikariDataSource(config);
    }
}

Spring Boot's DataSourceAutoConfiguration sees your DataSource bean, the @ConditionalOnMissingBean(DataSource.class) condition fails, and the auto-configured bean is not created. Your configuration applies.

The subtlety: @ConditionalOnMissingBean checks the application context for the bean type at the point the condition is evaluated. User-defined beans are processed before auto-configuration classes — this is why defining your own bean reliably suppresses the auto-configured one.

Override pattern 2: properties

Most auto-configuration behavior is controlled by @ConfigurationProperties classes. Overriding a property changes the behavior without defining a new bean:

# Override auto-configured connection pool settings
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      connection-timeout: 30000
      leak-detection-threshold: 60000
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        jdbc:
          batch_size: 50

The auto-configuration still applies — but its behavior is shaped by the properties rather than its defaults. This is the right choice when the auto-configured infrastructure is correct but needs tuning.

Override pattern 3: exclude specific auto-configuration

When you want to prevent auto-configuration from applying entirely — not override a specific bean, but disable the entire auto-configuration class:

@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class,
    DataSourceTransactionManagerAutoConfiguration.class
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Or via properties (useful when the auto-configuration class is in a dependency you don't control):

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

Exclusion is appropriate when the auto-configuration conflicts with a manual setup and @ConditionalOnMissingBean overrides are insufficient. A common case: an application that uses multiple DataSource beans for different databases — Spring Boot's single DataSource auto-configuration doesn't fit, so exclude it and wire the data sources manually.

Auto-configuration ordering

Auto-configuration classes are not applied in arbitrary order. The @AutoConfiguration annotation (and @AutoConfigureBefore, @AutoConfigureAfter) controls ordering between auto-configuration classes:

@AutoConfiguration(after = DataSourceAutoConfiguration.class)
public class HibernateJpaAutoConfiguration {
    // Applies after DataSource is available — can safely reference DataSource
}

User-defined beans and @Configuration classes are processed before auto-configuration classes. This is the ordering guarantee that makes @ConditionalOnMissingBean reliable — user beans exist when auto-configuration conditions are evaluated.

The ordering within user-defined @Configuration classes: Spring processes them in the order they're registered, which is influenced by component scanning order (not alphabetical, not predictable). If two @Configuration classes define beans where order matters, use @DependsOn or restructure to eliminate the order dependency.

Writing your own auto-configuration

Auto-configuration isn't just for framework code — it's the right mechanism for wiring shared infrastructure in libraries used across multiple Spring Boot applications:

// In your shared library
@AutoConfiguration
@ConditionalOnClass(AuditService.class)
@ConditionalOnMissingBean(AuditService.class)
@EnableConfigurationProperties(AuditProperties.class)
public class AuditAutoConfiguration {

    @Bean
    public AuditService auditService(AuditProperties properties) {
        return new DefaultAuditService(properties.getStorageBackend());
    }
}

Register it in your library's META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:

com.example.audit.AuditAutoConfiguration

Applications that include your library on the classpath get AuditService automatically. Applications that define their own AuditService bean get their version instead. The library's behavior is replaceable without modification.

The conditions evaluation report — the diagnostic tool

When a bean isn't being created and you don't know why, the conditions evaluation report shows exactly which conditions passed and which failed:

# application.properties
debug=true
# or
logging.level.org.springframework.boot.autoconfigure=DEBUG

This produces output like:

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.MongoClient' (OnClassCondition)

Exclusions:
-----------
None

Unconditional classes:
----------------------
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration

"Positive matches" shows what applied. "Negative matches" shows what didn't and why. "Exclusions" shows what was explicitly excluded.

The three diagnostic questions the report answers:

  1. Why isn't my bean being created? Look in negative matches — find your auto-configuration class and read the failed condition.
  2. Why are there two beans of the same type? Look in positive matches — both your bean definition and the auto-configured bean may be applying.
  3. What auto-configuration is the classpath triggering? Scan positive matches for unexpected auto-configuration.

The override decision

The three patterns serve different needs:

Define your own bean (@ConditionalOnMissingBean) when you need different behavior or a different implementation — a custom DataSource with specific pool settings, a custom ObjectMapper with different serialization configuration.

Override properties when the auto-configured implementation is correct but needs tuning — Hikari pool size, JPA batch size, cache TTLs.

Exclude the auto-configuration when it conflicts with a manual setup or when its presence would cause errors — multiple data sources, custom transaction management, third-party infrastructure that conflicts with Spring Boot's defaults.

The conditions evaluation report is the diagnostic tool for all three — it shows what applied, what didn't, and why. With it, auto-configuration is a transparent system with inspectable behavior, not a black box.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Scope Creep Is Not the Client's Fault. It Is a Communication Problem.

Scope creep does not happen because clients are difficult. It happens because the original scope was never clearly enough defined — and that is usually the contractor's responsibility.

Read more

What Separates a €50/hr Contractor From a €150/hr Contractor

The gap between a €50 rate and a €150 rate is not three times the technical skill. It is a specific combination of positioning, communication, and demonstrated value that most contractors are never taught to build.

Read more

Optimistic Locking in Hibernate — @Version, Retry Strategies, and Conflict Resolution

Concurrent updates to the same entity without coordination produce lost updates — the last write wins and intermediate changes are silently discarded. Optimistic locking detects this at commit time. Here is how it works and how to handle the conflicts it surfaces.

Read more

How to Position Yourself as a Specialist Instead of a Generalist

Being good at many things is an asset in a job. In contracting, it is a liability — because clients hire for specific problems, not general capability.

Read more