Problem

I have two separate classes marked as @Configuration, both set up beans with bean factory methods annotated with @Bean (please correct me if I have the wording off, I'm not an expert).

The first:

        @Configuration
        public class KidneyConfig {
            @Bean
            KidneyBean makeKidneyBean() { return new KidneyBean() }
        }

The dependent one:

        public class ChiliConfig {
             @ConditionalOnBean(KidneyBean.class)
             @Bean
             Chili makeChili(KidneyBean kidneyBean) { return new Chili(kidneyBean); ]
        }

The problem: Building everything as an application into a jar file (spring boot application, but I don't think that plays a role here) I can sometimes start the application, sometimes it will fail claiming that the condition failed.

Analysis

(I learned Spring internals on the go analyzing this bug, so please bear with inaccuracies):

When an ApplicationContext is built it will call refresh, and from there do a classpath scan to detect beans. The classpath scan is order dependent, so the bean definitions are in different order in the ApplicationContext.

Later in refresh() Spring will happen to call the ConfigurationClassParser which will scan @Configuration classes for @Bean methods. If it finds one it will use ConfigurationClass to locate the methods annotated with @Bean.

ConfigurationClass in turn will evaluate the Conditionals right away in this moment, so if my KidneyBean was already scanned by ConfigurationClass then everything will start up fine.

But if the Chili is scanned earlier than the KidneyBean, then the @ConditionalOnBean will fail - although just a moment later the ConfigurationClassParser will load the KidneyBean.

I'm not qualified to suggest how to fix this.

(Debugging this I found there's a post processor that validates for common problems - if using @ConditionalOnBean is not supported in the way I did, then one solution might be making the post processor detect that situation and complain during startup.)

Comment From: froh42

Sorry, for the changes in the title, had a few typos in the comment after a long debugging session.

Comment From: froh42

I have pushed a small demo showing this problem to https://github.com/froh42/spring-issue-demo-26964.

Its readme tries to explain it again, now that I'm not sleepy.

The demo application has both a ChiliConfig and a KidneyConfig in the situation described above. Starting it, the start will fail because ChiliConfig and its ConditionalOnBean are evaluated before KidneyConfig.

Renaming the KidneyConfig to AAAKidneyConfig will result in the KindneyConfig being loaded before ChiliConfig and the application will run.

See https://github.com/froh42/spring-issue-demo-26964/tree/master/src/main/java/com/example/problem for the code

Comment From: sbrannen

Hi @froh42,

I am not quite sure why you are using @ConditionalOnBean in your sample application.

If you remove the use of @ConditionalOnBean from your makeChiliBean() method, your application starts up correctly.

Please note that declaring the dependency on a KidneyBean via a method parameter in makeChiliBean() instructs Spring that makeChiliBean() cannot be invoked without a KidneyBean present in the ApplicationContext.

Does that suit your needs?

If not, please explain what you are trying to achieve by using @ConditionalOnBean.

Comment From: froh42

Hi @sbrannen , yes in the sample application I don't need the @ConditionalOnBean at all - and in my real application I have worked around the problem by switching away @ConditionalOnBean to @ConditionalOnProperty (using a property to control that part of the application is a valid solution for me).

The sample application includes the @ConditionalOnBean, because it is simply required to trigger the problem. Yes, when I remove it, it starts up correctly.

The sample is just the most reduced version of some code I could come up with to showcase the problem.

I did find this by finding out (and working around) the issue that the applicaton my team is developing did not start up in certain cases. I can't publish that one here, unfortunately.

What I can do: In the demo application, I can document the place where to set a breakpoint and condition for the breakpoint, so it is easy to debug to exactly the place where I think the problem occurs. I'll do that right away and add another comment, then.

Comment From: froh42

Hi @sbrannen again, I updated the https://github.com/froh42/spring-issue-demo-26964/blob/master/readme.md on the demo app with what I found out about where the problem exactly happens. (Stack traces, screenshots and my interpretation of things).

Comment From: ttddyy

@froh42 I had bit by similar case before. You should only use @ConditionalOnBean in auto-configuration classes as it comes from autoconfigure module in Spring Boot.

@ConditionalOnBean is dependent on the order of processing @Configuration classes.

The javadoc clearly states the order dependency and recommends the usage in auto configurations.

The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.

The auto-configuration classes are guaranteed to be processed after user configurations. Also, it has a mechanism to force relative orders within auto configurations by @AutoConfigure[Before|After].

Without a mechanism to order the configuration classes, the order of processing the @Configuration class that has @ConditionalOnBean is not deterministic. Therefore, you should only use it within auto configuration as it is the intended usage of the condition.

Comment From: froh42

@ttddyy yes, I do aggree. Of course I didn't re-read the documentation before applying the @ConditionalOnBean

That's why a good mitigation for the problem might just be printing just a warning if you DO use @ConditionalOnBean in this manner - this would have shortened debugging time. I do think a log.warning could be called in that case, that just says that.

People like me - stumbling into this trap - would then find at least a string to google for and find the documentation.

To the spring maintainers: As I did something that is not recommended and well documented, I think spring behavior won't be changed. Do you think, detecting this situation and adding a warning log message (or event debug log message, when you start with --debug argument) might be a good call? I learned my lesson, but other might experience this as well.

Comment From: sbrannen

I have worked around the problem by switching away @ConditionalOnBean to @ConditionalOnProperty (using a property to control that part of the application is a valid solution for me).

Have you considered using bean definition profiles (@Profile(...)) instead of @ConditionalOnProperty?

I think profiles might be a better fit for your use case.

As @ttddyy pointed out, @ConditionalOnBean is not intended to be used the way you were using it.

@ttddyy, thanks for providing your detailed answer!

Do you think, detecting this situation and adding a warning log message (or event debug log message, when you start with --debug argument) might be a good call? I learned my lesson, but other might experience this as well.

Since @ConditionalOnBean is a Spring Boot feature, I'll let the Spring Boot team decide if they wish to implement some form of warning for using @ConditionalOnBean in a @Configuration class that is not participating in auto-configuration.

Comment From: wilkinsona

Thanks for the suggestion, @froh42. Unfortunately, there's no way for us to detect if @ConditionalOnBean has been used in a regular @Configuration class or a @Configuration class that is or has been imported by an auto-configuration class. The current warnings in the javadoc and reference documentation will have to suffice.