Since Spring Boot 2.1.0 bean overriding mechanism is disabled by default. There is a dedicated flag for use in properties file: spring . main . allow - bean - definition - overriding . Nevertheless, if you have not heard about bean overriding before, the article is still worth reading. Firstly, the consequences of changing the default behavior in newer versions of Spring may strike you when migrating from older versions. Secondly, the post is based on a real case study from our experience and provides some insight on how Spring operates and what mistakes you should avoid.

„When Two Worlds collide

So who will survive

There’s no place to hide

When two worlds collide”

― Iron Maiden, “When Two Worlds Collide”. Virtual XI.

Let’s face it – there are features of Spring that many of us probably do not know about. With such amount of code and numerous pages of documentation as Spring framework has, it is virtually impossible to know them all.

Today I would like to tell you about one mechanism of Spring IoC container that I think is a bit less known amongst programmers. We were not aware of it and it had caused us a bit of confusion lately. It seemed at first as if one of our beans had somehow disappeared.

Case study

In our case we were responsible for implementing integration with a new client, which provided REST API for its services. The first thing we did was writing a client to introduce a layer of abstraction over the external API. Because the message format was JSON, the configuration class for the REST client module contained declaration of a customized ObjectMapper bean from Jackson library. Let’s say this first module was called external-api-client.

The second module we implemented was process-executor, which was responsible for executing processes for this particular client. What is crucial is that its configuration contained second bean of type ObjectMapper. This second mapper had to have a bit different configuration and was supposed to serialize data which we save in our database. We store it for audit purposes and also because sometimes we have to send the same request again.

Of course the external-api-client module configuration was supposed to be imported into the configuration class of process-executor. Knowing that we will have two beans of the same type (two ObjectMappers) we had annotated them with @Qualifier annotation to inject proper instances into desired services. As the result, our whole configuration consisted of classes similar to the ones presented below:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration public class ExternalApiClientConfig { @Bean public ExternalApiClient externalApiClient ( @Qualifier ( "externalApiObjectMapper" ) ObjectMapper objectMapper ) { return new ExternalApiClient ( objectMapper ) ; } @Bean @Qualifier ( "externalApiObjectMapper" ) public ObjectMapper objectMapper ( ) { return new ObjectMapper ( ) ; } // some other beans }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration @Import ( { ExternalApiClientConfig . class } ) public class ProcessExecutorConfig { @Bean public ProcessExecutor processExecutor ( ExternalApiClient externalApiClient , @Qualifier ( "requestSerializer" ) ObjectMapper objectMapper ) { return new ProcessExecutor ( externalApiClient , objectMapper ) ; } @Bean @Qualifier ( "requestSerializer" ) public ObjectMapper objectMapper ( ) { return new ObjectMapper ( ) ; } // some other beans }

Our App class was quite straightforward and looked more or less like this one:

1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @Import ( { ProcessExecutorConfig . class } ) public class App { public static void main ( String [ ] args ) { SpringApplication app = new SpringApplication ( App . class ) ; ConfigurableApplicationContext ctx = app . run ( ) ; } }

To our surprise running the application configured this way ended up in a crash with log output resembling following one:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 . ___ _ _ _ _ _ _ / \ \ / __ _ '_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | ' _ | '_| | ' _ \ / _ ` | \ \ \ \ \ \ / ___ ) | | _ ) | | | | | || ( _ | | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.10.RELEASE) 2018-03-07 17:46:54.624 INFO 16382 --- [ main] com.mfedkowicz.App : Starting App on mfedkowicz-Vostro-5568 with PID 16382 (/home/mfedkowicz/tratif/bean-overriding/process-executor/target/classes started by mfedkowicz in /home/mfedkowicz/tratif/bean-overriding) 2018-03-07 17:46:54.627 INFO 16382 --- [ main] com.mfedkowicz.App : No active profile set, falling back to default profiles: default 2018-03-07 17:46:54.727 INFO 16382 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@71623278: startup date [Wed Mar 07 17:46:54 CET 2018]; root of context hierarchy 2018-03-07 17:46:55.175 INFO 16382 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean ' objectMapper ' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=processExecutorConfig; factoryMethodName=objectMapper; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mfedkowicz/ProcessExecutorConfig.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=externalApiClientConfig; factoryMethodName=objectMapper; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mfedkowicz/service/ExternalApiClientConfig.class]] 2018-03-07 17:46:55.608 WARN 16382 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ' processExecutor ' defined in class path resource [com/mfedkowicz/ProcessExecutorConfig.class]: Unsatisfied dependency expressed through method ' processExecutor ' parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ' com . fasterxml . jackson . databind . ObjectMapper ' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier(value=requestSerializer)} 2018-03-07 17:46:55.615 INFO 16382 --- [ main] utoConfigurationReportLoggingInitializer : Error starting ApplicationContext. To display the auto-configuration report re-run your application with ' debug ' enabled. 2018-03-07 17:46:55.730 ERROR 16382 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPLICATION FAILED TO START *************************** Description: Parameter 1 of method processExecutor in com.mfedkowicz.ProcessExecutorConfig required a bean of type ' com . fasterxml . jackson . databind . ObjectMapper ' that could not be found. - Bean method ' jacksonObjectMapper ' not loaded because @ConditionalOnClass did not find required class ' org . springframework . http . converter . json . Jackson2ObjectMapperBuilder ' Action: Consider revisiting the conditions above or defining a bean of type ' com . fasterxml . jackson . databind . ObjectMapper ' in your configuration . Process finished with exit code 1

It looked as if Spring could not find the right ObjectMapper to inject into our ProcessExecutor! We were confused at first. The declaration of the missing bean was just below the declaration of ProcessExecutor bean. We checked if we did not make a typo in qualifier name, but all seemed fine. So what had actually happened?

Explanation — bean overriding mechanism

After some time we realized that there was a problem with our Spring context configuration. We missed the fact that our two ObjectMapper beans were constructed by methods with the same name, hence the beans themselves also had conflicting names. It did not struck us at first because we assumed that Spring would throw an exception with a meaningful message informing about the bean name collision. Well, we were wrong.

We fixed the name collision and everything worked like expected. Nevertheless, we were still eager to find out what actually had happened under the hood. I did some debugging and digging over the Internet and finally I got to know!

What was going on in our situation could have been summarized as follows:

Spring created ObjectMapper bean from ProcessExecutorConfig (the one with @Qualifier ( "requestSerializer" ) ). Later on Spring encountered ObjectMapper bean declaration from ExternalApiClientConfig . Because the names of the beans were the same the previously instantiated bean ( @Qualifier ( "requestSerializer" ) ) was overridden by the new one ( @Qualifier ( "externalApiObjectMapper" ) ). As the effect, from now on the context contained only one bean of type ObjectMapper qualified as “externalApiObjectMapper”. When it came time to inject ObjectMapper into ProcessExecutor no bean with appropriate qualifier was found.

So the mechanism which caused us so much confusion is called bean overriding. It is used when Spring encounters a declaration of a bean with the same name as another bean already existing in the context. And it actually informs about it in the logs. We just did not scan the log output carefully enough, cause the detailed information about this whole overriding was there:

1 2018 - 03 - 07 17 : 46 : 55.175 INFO 16382 -- - [ main ] o . s . b . f . s . DefaultListableBeanFactory : Overriding bean definition for bean 'objectMapper' with a different definition : replacing [ Root bean : class [ null ] ; scope = ; abstract = false ; lazyInit = false ; autowireMode = 3 ; dependencyCheck = 0 ; autowireCandidate = true ; primary = false ; factoryBeanName = processExecutorConfig ; factoryMethodName = objectMapper ; initMethodName = null ; destroyMethodName = ( inferred ) ; defined in class path resource [ com / mfedkowicz / ProcessExecutorConfig . class ] ] with [ Root bean : class [ null ] ; scope = ; abstract = false ; lazyInit = false ; autowireMode = 3 ; dependencyCheck = 0 ; autowireCandidate = true ; primary = false ; factoryBeanName = externalApiClientConfig ; factoryMethodName = objectMapper ; initMethodName = null ; destroyMethodName = ( inferred ) ; defined in class path resource [ com / mfedkowicz / service / ExternalApiClientConfig . class ] ]

At that point I assumed that there is some strict rule regarding bean overriding. I imagined that a bean with the same name from imported configuration is always overridden by the one declared in the configuration class it is imported into (like it happened in our case). Well, I was wrong there too.

The only rule is this: bean with the same name as another one, which is processed later, overrides the older one.

Sanity goes down

Actually it took me some time to come up with the above rule. When I was preparing to write this article I implemented a couple of examples, which behaved in predictable manner (the order of overriding was the same). But then I threw all my examples away and generated a new one, which I thought was better describing our case. To my surprise this last one behaved completely different. If for these previous examples the imported beans were loosing the overriding battle, then for this last one it was the other way around.

I spent almost two hours to debug the last example to find out what was different and why this time things were different. And it turned out that the only difference was the order in which the configuration classes were loaded by a classloader!

For instance, for the configuration classes presented at the beginning of this article, if I changed the name from ExternalApiClientConfig to AExternalApiClientConfig the log output would be different:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 . ___ _ _ _ _ _ _ / \ \ / __ _ '_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | ' _ | '_| | ' _ \ / _ ` | \ \ \ \ \ \ / ___ ) | | _ ) | | | | | || ( _ | | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.10.RELEASE) 2018-03-07 17:49:05.268 INFO 16491 --- [ main] com.mfedkowicz.App : Starting App on mfedkowicz-Vostro-5568 with PID 16491 (/home/mfedkowicz/tratif/bean-overriding/process-executor/target/classes started by mfedkowicz in /home/mfedkowicz/tratif/bean-overriding) 2018-03-07 17:49:05.271 INFO 16491 --- [ main] com.mfedkowicz.App : No active profile set, falling back to default profiles: default 2018-03-07 17:49:05.383 INFO 16491 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@71623278: startup date [Wed Mar 07 17:49:05 CET 2018]; root of context hierarchy 2018-03-07 17:49:05.797 INFO 16491 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean ' objectMapper ' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=AExternalApiClientConfig; factoryMethodName=objectMapper; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mfedkowicz/AExternalApiClientConfig.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=processExecutorConfig; factoryMethodName=objectMapper; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mfedkowicz/ProcessExecutorConfig.class]] 2018-03-07 17:49:06.177 WARN 16491 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ' externalApiClient ' defined in class path resource [com/mfedkowicz/AExternalApiClientConfig.class]: Unsatisfied dependency expressed through method ' externalApiClient ' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ' com . fasterxml . jackson . databind . ObjectMapper ' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier(value=externalApiObjectMapper)} 2018-03-07 17:49:06.189 INFO 16491 --- [ main] utoConfigurationReportLoggingInitializer : Error starting ApplicationContext. To display the auto-configuration report re-run your application with ' debug ' enabled. 2018-03-07 17:49:06.327 ERROR 16491 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPLICATION FAILED TO START *************************** Description: Parameter 0 of method externalApiClient in com.mfedkowicz.AExternalApiClientConfig required a bean of type ' com . fasterxml . jackson . databind . ObjectMapper ' that could not be found. - Bean method ' jacksonObjectMapper ' not loaded because @ConditionalOnClass did not find required class ' org . springframework . http . converter . json . Jackson2ObjectMapperBuilder ' Action: Consider revisiting the conditions above or defining a bean of type ' com . fasterxml . jackson . databind . ObjectMapper ' in your configuration . Process finished with exit code 1

Of course it does not depend only on the name of the class. It can vary also with the package or module in which two classes reside. In other words: the amount of factors which can affect the order of processing bean declarations makes it almost impossible to predict which bean will override another. This is especially true for more complex context configurations with multiple imports.

The only thing I am pretty sure of is that without component scanning this order becomes deterministic — with imported configuration classes being processed first. So for instance getting rid of @SpringBootApplication annotation from the App class will somewhat stabilize the situation.

Other situations in which it might hurt you

All of the above observations are just the effect of my experiments with the configuration classes. Frankly speaking I was not able to find anything about bean overriding in the official Spring documentation (if it is somewhere there, please point me to the place). So in the end this whole situation is a bit confusing.

Further thinking about it I realized there are at least two other situations, than the one which we encountered, in which bean overriding might cause some problems:

When you have two beans with the same name in different configuration classes but without qualifiers — in such a case the application will just run without exceptions, but you will end up with a wrong bean in at least one place. In our case for instance if we did not have qualifiers, we would have end up with wrong ObjectMapper serializing our requests into database. Without thorough test such situation could stay unnoticed for a long time and cause serious issues. When you have two beans with the same name but of different types. At least in such a situation the result will be the crash of the application during start time. That is a case similar to our one with qualifiers.

With the size of the application the complexity will increase. There will be potentially multiple configuration classes spanned across different modules or even projects. So it could be really hard to investigate specific case.

Solution

The obvious solution to prevent such overriding problems is to name beans as specific as possible. Instead of naming both factory methods in our app objectMapper we should have used more descriptive names as for instance: externalApiObjectMapper and requestSerializer.

But even if you are careful, there is still a risk that you can choose the same bean name as a one imported from a library or chosen by another developer who implemented different module. And the most danger case here is when the app starts without any error and then behaves in a different way than expected. There is still a log message telling that the overriding took place, but it is easy to miss. Can we do anything about it?

Turning off bean overriding

Fortunately bean overriding is hidden behind a flag of DefaultListableBeanFactory which can be set by a user. If we do want to turn it off all we need to do is implement our custom ApplicationContextInitializer, like in the example below:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @SpringBootApplication @Import ( { ProcessExecutorConfig . class } ) public class App { public static void main ( String [ ] args ) { SpringApplication app = new SpringApplication ( App . class ) ; app . addInitializers ( new CustomAppCtxInitializer ( ) ) ; ConfigurableApplicationContext ctx = app . run ( ) ; } private static class CustomAppCtxInitializer implements ApplicationContextInitializer <GenericApplicationContext> { @Override public void initialize ( GenericApplicationContext applicationContext ) { applicationContext . getDefaultListableBeanFactory ( ) . setAllowBeanDefinitionOverriding ( false ) ; } } }

From now on, every time Spring will encounter bean with the same name as the one already existing in the context, exception will be thrown and application will crash during the start time:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 . ___ _ _ _ _ _ _ / \ \ / __ _ '_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | ' _ | '_| | ' _ \ / _ ` | \ \ \ \ \ \ / ___ ) | | _ ) | | | | | || ( _ | | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.10.RELEASE) 2018-03-07 17:50:13.638 INFO 16571 --- [ main] com.mfedkowicz.App : Starting App on mfedkowicz-Vostro-5568 with PID 16571 (/home/mfedkowicz/tratif/bean-overriding/process-executor/target/classes started by mfedkowicz in /home/mfedkowicz/tratif/bean-overriding) 2018-03-07 17:50:13.641 INFO 16571 --- [ main] com.mfedkowicz.App : No active profile set, falling back to default profiles: default 2018-03-07 17:50:13.738 INFO 16571 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@71623278: startup date [Wed Mar 07 17:50:13 CET 2018]; root of context hierarchy 2018-03-07 17:50:14.126 WARN 16571 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name ' objectMapper ' defined in class path resource [com/mfedkowicz/ProcessExecutorConfig.class]: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=processExecutorConfig; factoryMethodName=objectMapper; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mfedkowicz/ProcessExecutorConfig.class]] for bean ' objectMapper ': There is already [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=AExternalApiClientConfig; factoryMethodName=objectMapper; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mfedkowicz/AExternalApiClientConfig.class]] bound. 2018-03-07 17:50:14.146 INFO 16571 --- [ main] utoConfigurationReportLoggingInitializer : Error starting ApplicationContext. To display the auto-configuration report re-run your application with ' debug ' enabled. 2018-03-07 17:50:14.161 ERROR 16571 --- [ main] o.s.boot.SpringApplication : Application startup failed org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name ' objectMapper ' defined in class path resource [com/mfedkowicz/ProcessExecutorConfig.class]: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=processExecutorConfig; factoryMethodName=objectMapper; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mfedkowicz/ProcessExecutorConfig.class]] for bean ' objectMapper ' : There is already [ Root bean : class [ null ] ; scope = ; abstract = false ; lazyInit = false ; autowireMode = 3 ; dependencyCheck = 0 ; autowireCandidate = true ; primary = false ; factoryBeanName = AExternalApiClientConfig ; factoryMethodName = objectMapper ; initMethodName = null ; destroyMethodName = ( inferred ) ; defined in class path resource [ com / mfedkowicz / AExternalApiClientConfig . class ] ] bound . at org . springframework . beans . factory . support . DefaultListableBeanFactory . registerBeanDefinition ( DefaultListableBeanFactory . java : 814 ) ~ [ spring - beans - 4.3.14.RELEASE.jar : 4.3.14.RELEASE ] at org . springframework . context . annotation . ConfigurationClassBeanDefinitionReader . loadBeanDefinitionsForBeanMethod ( ConfigurationClassBeanDefinitionReader . java : 266 ) ~ [ spring - context - 4.3.14.RELEASE.jar : 4.3.14.RELEASE ] at org . springframework . context . annotation . ConfigurationClassBeanDefinitionReader . loadBeanDefinitionsForConfigurationClass ( ConfigurationClassBeanDefinitionReader . java : 140 ) ~ [ spring - context - 4.3.14.RELEASE.jar : 4.3.14.RELEASE ] at org . springframework . context . annotation . ConfigurationClassBeanDefinitionReader . loadBeanDefinitions ( ConfigurationClassBeanDefinitionReader . java : 116 ) ~ [ spring - context - 4.3.14.RELEASE.jar : 4.3.14.RELEASE ] at org . springframework . context . annotation . ConfigurationClassPostProcessor . processConfigBeanDefinitions ( ConfigurationClassPostProcessor . java : 320 ) ~ [ spring - context - 4.3.14.RELEASE.jar : 4.3.14.RELEASE ] at org . springframework . context . annotation . ConfigurationClassPostProcessor . postProcessBeanDefinitionRegistry ( ConfigurationClassPostProcessor . java : 228 ) ~ [ spring - context - 4.3.14.RELEASE.jar : 4.3.14.RELEASE ] at org . springframework . context . support . PostProcessorRegistrationDelegate . invokeBeanDefinitionRegistryPostProcessors ( PostProcessorRegistrationDelegate . java : 272 ) ~ [ spring - context - 4.3.14.RELEASE.jar : 4.3.14.RELEASE ] at org . springframework . context . support . PostProcessorRegistrationDelegate . invokeBeanFactoryPostProcessors ( PostProcessorRegistrationDelegate . java : 92 ) ~ [ spring - context - 4.3.14.RELEASE.jar : 4.3.14.RELEASE ] at org . springframework . context . support . AbstractApplicationContext . invokeBeanFactoryPostProcessors ( AbstractApplicationContext . java : 687 ) ~ [ spring - context - 4.3.14.RELEASE.jar : 4.3.14.RELEASE ] at org . springframework . context . support . AbstractApplicationContext . refresh ( AbstractApplicationContext . java : 525 ) ~ [ spring - context - 4.3.14.RELEASE.jar : 4.3.14.RELEASE ] at org . springframework . boot . SpringApplication . refresh ( SpringApplication . java : 693 ) ~ [ spring - boot - 1.5.10.RELEASE.jar : 1.5.10.RELEASE ] at org . springframework . boot . SpringApplication . refreshContext ( SpringApplication . java : 360 ) ~ [ spring - boot - 1.5.10.RELEASE.jar : 1.5.10.RELEASE ] at org . springframework . boot . SpringApplication . run ( SpringApplication . java : 303 ) ~ [ spring - boot - 1.5.10.RELEASE.jar : 1.5.10.RELEASE ] at com . mfedkowicz . App . main ( App . java : 17 ) [ classes / : na ] Process finished with exit code 1

It might be a good idea to turn it off by default. If we do so then we will get to know about every bean name conflict right away.

Do we really need so many beans?

Using Spring tempts to make beans from almost any class. Up until now all of the considerations assumed that we still want to register two conflicting beans in the context. But in fact many of such overriding problems arise from the mistake of making beans from objects, which should not necessarily be managed by Spring.

Let’s take our case for example — we had two ObjectMappers, but each of them was injected only into one class where it was supposed to be used. We did not actually need to make beans from them. Actually it was just polluting the context of Spring for which we paid with our time and a lot of confusion. After getting to know what happened we ended up with such a solution:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration @Import ( { ExternalApiClientConfig . class } ) public class ProcessExecutorConfig { @Bean public ProcessExecutor processExecutor ( ExternalApiClient externalApiClient ) { return new ProcessExecutor ( externalApiClient , objectMapper ( ) ) ; } private ObjectMapper objectMapper ( ) { return new ObjectMapper ( ) ; } }

Instead of making bean from ObjectMapper (only to inject it into one method above) we implemented simple factory method for it. Now, the only interested class — ProcessExecutorConfig — knows about it and ProcessExecutor will be built correctly without any complaints from Spring side.

What I am trying to say is that from time to time it is good to take a step back and reconsider if all our bean declarations are really valid ones. If we inject some of the beans into only one place, then it may be beneficial to not pollute Spring context with them.

Summary

In this article I have described one of the less known mechanisms of Spring IoC container, which is bean overriding. In a nutshell, it happens when Spring encounters bean with the same name as one of the beans already registered in the context. In such a situation the older bean is overriden by the newer one.

As our case study shows, this mechanism can cause sometimes a bit of confusion — especially when the application does have a complex context configuration. Besides the case presented in this article there are also other situations, in which you may observe unintended behavior due to bean overriding. And the fact that the overriding order depends on too many variables can make it really hard to tell what is really going on under the hood.

To prevent such situations you can consider making your bean names as specific as possible. It is also worth to think if all your objects need to be registered in the Spring Context at all. In many cases the good default is to disable the bean overriding mechanism at all — then Spring will throw more meaningful exceptions whenever a collision happens.

I hope that you will find this article interesting and helpful. Who knows, maybe it will save some of your time in the future. In any case I wish you that your Spring context will be always clean!

Please follow and like us: