BeanDefinitionOverrideException in Boot 3

Why Spring Boot 3 breaks silently-overriding bean configurations from Boot 2.x — and how to resolve bean name conflicts cleanly.

Framework
Spring Boot 3.x
Runtime
Java 21 LTS
Stability
Enterprise grade

Overview

In Spring Boot 2.x, defining two beans with the same name caused the second to silently replace the first — a behavior that could hide serious misconfiguration. Spring Boot 3 disables this by default, converting silent overrides into a hard startup failure: BeanDefinitionOverrideException. This is a deliberate safety improvement that surfaces hidden bean conflicts during migration or when library auto-configurations clash with application beans.

Symptom

exception_report.logFatal
org.springframework.beans.factory.support.BeanDefinitionOverrideException: 
  Invalid bean definition with name 'dataSource' defined in 
  com.example.config.DataSourceConfig: 
  
  Cannot register bean definition [Root bean: class [com.zaxxer.hikari.HikariDataSource]]
  since there is already [Root bean: class [org.apache.tomcat.jdbc.pool.DataSource]] 
  bound.
  
  Action: Consider renaming one of the beans or enabling overriding by setting 
  spring.main.allow-bean-definition-overriding=true

Root cause

Multiple @Configuration classes are producing @Beans with the same name — either because the application defines a bean that conflicts with a library's auto-configured bean, or because two configurations in a large codebase accidentally share a bean name. Spring Boot 3's stricter default makes this an application startup failure rather than a silent runtime surprise.

Resolution

  1. Rename the conflicting bean with a unique, descriptive name (preferred: always the first option).

  2. Use @ConditionalOnMissingBean so your custom bean is only registered if no other bean of that type exists.

  3. Use @Primary to signal which bean should be injected by default when multiple candidates exist.

  4. Add @Qualifier to injection sites to select a specific bean by name.

  5. Setting spring.main.allow-bean-definition-overriding=true restores Boot 2 behavior but is a last resort — it re-enables silent overrides.

Production implementation
SafeJava 21
// ❌ CONFLICT — two beans named 'dataSource' at startup
class="hi-ann">@Configuration
public class AppConfig {
    class="hi-ann">@Bean
    public DataSource dataSource() { // Name: "dataSource"
        return new HikariDataSource(hikariConfig());
    }
}

// ✅ FIX 1: Unique naming
class="hi-ann">@Configuration
public class AppConfig {
    class="hi-ann">@Bean("appDataSource")
    public DataSource dataSource() { ... }
}

// ✅ FIX 2: @ConditionalOnMissingBean (Library-safe override pattern)
class="hi-ann">@Configuration
public class AppConfig {
    class="hi-ann">@Bean
    class="hi-ann">@ConditionalOnMissingBean(DataSource.class) // Only register if nothing else provides one
    public DataSource dataSource() { ... }
}

// ✅ FIX 3: @Primary (Preferred injection candidate)
class="hi-ann">@Bean
class="hi-ann">@Primary
public DataSource primaryDataSource() { ... }

class="hi-ann">@Bean("secondaryDataSource")
public DataSource secondaryDataSource() { ... }

// Injection uses @Primary automatically:
// @Autowired DataSource ds; → gets primaryDataSource

// ⚠️ ESCAPE HATCH (Boot 2 compatibility only — avoid in new code)
# application.properties
spring.main.allow-bean-definition-overriding=true

Deep dive

Bean Naming Rules

By default, a class="hi-ann">@Bean method's name is the method name. A class annotated with class="hi-ann">@Component, class="hi-ann">@Service, or class="hi-ann">@Repository uses the decapitalized class name. When two beans share the same name in the same ApplicationContext, Boot 3 fails fast at startup rather than allowing a random winner.

Auto-Configuration Conflicts

The most common source of this exception in Boot 3 migrations is the application defining a bean that Boot's own auto-configuration also tries to provide — e.g., a custom DataSource clashing with DataSourceAutoConfiguration. Use class="hi-ann">@ConditionalOnMissingBean in your configuration to yield to auto-configuration, or use spring.autoconfigure.exclude to disable the conflicting auto-configuration entirely.

Bean Naming Conventions at Scale

In large codebases with many shared modules, prefix bean names with the module name to avoid collisions: class="hi-ann">@Bean("payments.dataSource"), class="hi-ann">@Bean("inventory.dataSource"). This is especially important when composing multiple Spring modules into a single application context.

Best practices

  1. Never use spring.main.allow-bean-definition-overriding=true in new projects — it trades a startup error for a silent runtime misconfiguration.

  2. Prefer @ConditionalOnMissingBean in all shared library configurations to make them override-safe by design.

  3. Add a startup smoke test to your CI pipeline that verifies ApplicationContext loads successfully — catches bean conflicts before they reach production.

FAQ

What causes BeanDefinitionOverrideException in Boot 3 in Spring Boot 3?
Multiple @Configuration classes are producing @Beans with the same name — either because the application defines a bean that conflicts with a library's auto-configured bean, or because two configurations in a large codebase accidentally share a bean name. Spring Boot 3's stricter default makes this an application startup failure rather than a silent runtime surprise.
How do I fix BeanDefinitionOverrideException in Boot 3?
Rename the conflicting bean with a unique, descriptive name (preferred: always the first option). Use @ConditionalOnMissingBean so your custom bean is only registered if no other bean of that type exists. Use @Primary to signal which bean should be injected by default when multiple candidates exist. Add @Qualifier to injection sites to select a specific bean by name. Setting spring.main.allow-bean-definition-overriding=true restores Boot 2 behavior but is a last resort — it re-enables silent overrides.
Best practice #1 for preventing Spring Context · Boot 3 errors?
Never use spring.main.allow-bean-definition-overriding=true in new projects — it trades a startup error for a silent runtime misconfiguration.
Best practice #2 for preventing Spring Context · Boot 3 errors?
Prefer @ConditionalOnMissingBean in all shared library configurations to make them override-safe by design.
Best practice #3 for preventing Spring Context · Boot 3 errors?
Add a startup smoke test to your CI pipeline that verifies ApplicationContext loads successfully — catches bean conflicts before they reach production.

Modular Application Context Design to Prevent Bean Conflicts

BeanDefinitionOverrideException is Spring Boot 3's way of making explicit a conflict that Spring Boot 2 silently resolved by allowing the later-registered bean to win. The behavior change was intentional and correct — silent overrides are a category of bug where test and production environments diverge because classpath ordering differs between them. Fixing the immediate error is straightforward; designing a module structure that prevents it from recurring requires more deliberate architectural thinking.

📐 Package-Scoped Component Scanning

The default behavior of @SpringBootApplication is to scan the package of the annotated class and all sub-packages. In a large modular monolith, this becomes a liability. Restrict your component scans using scanBasePackages and treat shared infrastructure as explicit dependencies imported via @Import(SharedDataSourceConfig.class) rather than discovered by scan.

🎯 Conditional Bean Registration

The @ConditionalOn* family is the correct tool for beans whose registration depends on environment, classpath, or property conditions. Annotating a library's default bean with @ConditionalOnMissingBean documents the override contract explicitly and prevents duplicate registration entirely.

🧱 Spring Modulith Enforcement

Spring Modulith (GA since Spring Boot 3.1) provides runtime module boundaries and test support that verifies those boundaries are not violated. Its @ApplicationModuleTest slice makes it impossible for a stray component scan to import a conflicting bean from an adjacent module.

For every @Bean method in a shared @Configuration class, document the bean's role, its expected override policy, and any ordering constraints in the JavaDoc of the configuration class — not in a wiki that goes stale. A context design that makes module boundaries visible, declares override contracts explicitly, and enforces structure through tests transforms BeanDefinitionOverrideException from a recurring fire drill into a one-time conversation.

Kodivio Team
Kodivio Team

AI & Web Development Specialists

The Kodivio team covers AI tools, automation, and modern web development based on real-world testing and hands-on experience.

Learn more about us →