'@ConditionalOnMissingBean and @ConditionalOnBean on the same type

I've got strange situation. I'm trying to write auto configuration class where I create bean if another bean exists or not so I've got conditional on bean and conditional on missing bean on same type..

Here is my code:

@Bean
@ConditionalOnMissingBean(
    type = {"com.example.Api"}
)
public ApiManager apiManager() {
    return new ApiManager() {
        public Result getValue(Request request) {
            throw new Exception("Api not provided.");
        }

        public Result setValue(Request request) {
            throw new Exception("Api not provided.");
        }
    };
}

@Bean
@ConditionalOnBean(
    type = {"com.example.Api"}
)
public ApiManager apiManager(Api api) {
    return new ApiManagerImpl(api);
}

The problem is that it does not check @ConditionalOnBean if it already checked in @ConditionalOnMissingBean that bean of type com.example.Api is not missing and then bean is not created.

And I get error like:

Parameter 2 of constructor in com.example.ServiceImpl required a bean of type 'com.example.ApiManager' that could not be found.
- Bean method 'apiManager' in 'ApiAutoConfiguration' not loaded because @ConditionalOnMissingBean (types: com.example.Api; SearchStrategy: all) found bean 'api'
- Bean method 'apiManager' in 'ApiAutoConfiguration' not loaded because @ConditionalOnMissingBean (types: com.example.Api; SearchStrategy: all) found bean 'api'


Solution 1:[1]

What you get is quite logical. Conditions are evaluated in order, that's why auto-configurations are processed after user-configuration.

If the context did what you're expecting, we'd have to evaluate all the conditions of all the bean definitions before doing anything. And that will defeat the purpose of an auto-configuration handling the fact a bean of a certain type was provided by a user (user config) or an auto-configuration.

You need to rewrite this auto-configuration to import things in order. One way to do that is to move each method on a package private config class and then do as follows:

@Configuration
@Import({ApiPresentConfiguration.class, ApiNotPresentConfiguration.class})

A better way to handle this really is to not do that at all and simply use ObjectProvider:

@Bean
public ApiManager apiManager(ObjectProvider<Api> api) {
    // Check if API is present and then return the appropriate implementation
}

Solution 2:[2]

try leveraging @Primary.

I am not sure how this would fit your use case but consider the following:

@Bean
public ApiManager apiManager() {
    return new ApiManager() {
        public Result getValue(Request request) {
            throw new Exception("Api not provided.");
        }

        public Result setValue(Request request) {
            throw new Exception("Api not provided.");
        }
    };
}

@Bean
@ConditionalOnBean(
    type = {"com.example.Api"}
)
@Primary
public ApiManager apiManager(Api api) {
    return new ApiManagerImpl(api);
}

Case 1: Api doesn't exist

  1. First bean acts as the default one and is used.
  2. Second bean is not created at all.

Case 2: Api does exist

  1. First bean is created (since it's the default) but not used.
  2. Second bean is created and you end up with 2 beans of type ApiManager; but since it has @Primary; it will be used.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Stephane Nicoll
Solution 2 Mahmoud K.