1. Introduction
The Spring Boot 2.1 upgrade surprised several people with unexpected occurrences of BeanDefinitionOverrideException. It can confuse some developers and make them wonder about what happened to the bean overriding behavior in Spring.
In this tutorial, we'll unravel this issue and see how best to address it.
2. Maven Dependencies
For our example Maven project, we need to add the Spring Boot Starter dependency:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.2.3.RELEASE</version> </dependency>
3. Bean Overriding
Spring beans are identified by their names within an ApplicationContext.
Thus, bean overriding is a default behavior that happens when we define a bean within an ApplicationContext which has the same name as another bean. It works by simply replacing the former bean in case of a name conflict.
Starting in Spring 5.1, the BeanDefinitionOverrideException was introduced to allow developers to automatically throw the exception to prevent any unexpected bean overriding. By default, the original behavior is still available which allows bean overriding.
4. Configuration Change for Spring Boot 2.1
Spring Boot 2.1 disabled bean overriding by default as a defensive approach. The main purpose is to notice the duplicate bean names in advance to prevent overriding beans accidentally.
Therefore, if our Spring Boot application relies on bean overriding, it is very likely to encounter the BeanDefinitionOverrideException after we upgrade the Spring Boot version to 2.1 and later.
In the next sections, we'll look at an example where the BeanDefinitionOverrideException would occur, and then we will discuss some solutions.
5. Identifying the Beans in Conflict
Let's create two different Spring configurations, each with a testBean() method, to produce the BeanDefinitionOverrideException:
@Configuration public class TestConfiguration1 { class TestBean1 { private String name; // standard getters and setters } @Bean public TestBean1 testBean(){ return new TestBean1(); } }
@Configuration public class TestConfiguration2 { class TestBean2 { private String name; // standard getters and setters } @Bean public TestBean2 testBean(){ return new TestBean2(); } }
Next, we will create our Spring Boot test class:
@RunWith(SpringRunner.class) @SpringBootTest(classes = {TestConfiguration1.class, TestConfiguration2.class}) public class SpringBootBeanDefinitionOverrideExceptionIntegrationTest { @Test public void whenBeanOverridingAllowed_thenTestBean2OverridesTestBean1() { Object testBean = applicationContext.getBean("testBean"); assertThat(testBean.getClass()).isEqualTo(TestConfiguration2.TestBean2.class); } }
Running the test produces a BeanDefinitionOverrideException. However, the exception provides us with some helpful information:
Invalid bean definition with name 'testBean' defined in ... ... com.baeldung.beandefinitionoverrideexception.TestConfiguration2 ... Cannot register bean definition [ ... defined in ... ... com.baeldung.beandefinitionoverrideexception.TestConfiguration2] for bean 'testBean' ... There is already [ ... defined in ... ... com.baeldung.beandefinitionoverrideexception.TestConfiguration1] bound.
Notice that the exception reveals two important pieces of information.
The first one is the conflicting bean name, testBean:
Invalid bean definition with name 'testBean' ...
And the second shows us the full path of the configurations affected:
... com.baeldung.beandefinitionoverrideexception.TestConfiguration2 ... ... com.baeldung.beandefinitionoverrideexception.TestConfiguration1 ...
As a result, we can see that two different beans are identified as testBean causing a conflict. Additionally, the beans are contained inside the configuration classes TestConfiguration1 and TestConfiguration2.
6. Possible Solutions
Depending on our configuration, Spring Beans have default names unless we set them explicitly.
Therefore, the first possible solution is to rename our beans.
There are some common ways to set bean names in Spring.
6.1. Changing Method Names
By default, Spring takes the name of the annotated methods as bean names.
Therefore, if we have beans defined in a configuration class, like our example, then simply changing the method names will prevent the BeanDefinitionOverrideException:
@Bean public TestBean1 testBean1() { return new TestBean1(); }
@Bean public TestBean2 testBean2() { return new TestBean2(); }
6.2. @Bean Annotation
Spring's @Bean annotation is a very common way of defining a bean.
Thus, another option is to set the name property of @Bean annotation:
@Bean("testBean1") public TestBean1 testBean() { return new TestBean1(); }
@Bean("testBean2") public TestBean1 testBean() { return new TestBean2(); }
6.3. Stereotype Annotations
Another way to define a bean is with stereotype annotations. With Spring's @ComponentScan feature enabled, we can define our bean names at the class level using the @Component annotation:
@Component("testBean1") class TestBean1 { private String name; // standard getters and setters }
@Component("testBean2") class TestBean2 { private String name; // standard getters and setters }
6.4. Beans Coming From 3rd Party Libraries
In some cases, it's possible to encounter a name conflict caused by beans originating from 3rd party spring-supported libraries.
When this happens, we should attempt to identify which conflicting bean belongs to our application, to determine if any of the above solutions can be used.
However, if we are unable to alter any of the bean definitions, then configuring Spring Boot to allow bean overriding can be a workaround.
To enable bean overriding, let's set the spring.main.allow-bean-definition-overriding property to true in our application.properties file:
spring.main.allow-bean-definition-overriding=true
By doing this, we are telling Spring Boot to allow bean overriding without any change to bean definitions.
As a final notice, we should be aware that it is difficult to guess which bean will have priority because the bean creation order is determined by dependency relationships mostly influenced in runtime. Therefore, allowing bean overriding can produce unexpected behavior unless we know the dependency hierarchy of our beans well enough.
7. Conclusion
In this tutorial, we explained what BeanDefinitionOverrideException means in Spring, why it suddenly appears, and how to address it after the Spring Boot 2.1 upgrade.
Like always, the complete source code of this article can be found over on GitHub.