I introduced to you the @Conditional annotation in Spring with the purpose of allowing us to define conditions for a bean to be initialized in the Spring container. We will need to implement the Condition interface with the abstract method matches() returning true or false to represent whether or not these conditions are met. Spring Boot provides us with a number of implementations for the Condition interface along with corresponding annotations that help us solve many practical problems when working with Spring Boot applications. One of them is the @ConditionalOnBean annotation.
Spring Boot’s @ConditionalOnBean annotation is used to let Spring know that the class or method annotated with this annotation can only initialize the bean in the Spring container if a bean of another class already exists in the Spring container.
For example, I have 2 simple classes as follows:
1 2 3 4 5 |
package com.huongdanjava.springboot; public record Clazz(String name) { } |
and:
1 2 3 4 5 |
package com.huongdanjava.springboot; public record Student(String name) { } |
To configure beans for the above classes in the Spring container, I will define an annotated class with the @Configuration annotation as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.huongdanjava.springboot; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ApplicationConfiguration { @Bean public Student student() { return new Student("Khanh"); } @Bean public Clazz clazz() { return new Clazz("A"); } } |
Now, if you get the Student information defined in the Spring container:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.huongdanjava.springboot.springbootconditionalonbean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringbootConditionalonbeanApplication implements CommandLineRunner { @Autowired private Student student; public static void main(String[] args) { SpringApplication.run(SpringbootConditionalonbeanApplication.class, args); } @Override public void run(String... args) throws Exception { System.out.println(student.name()); } } |
you will see the following results:
If I now want the Student class bean to be initialized only if the Clazz class bean already exists in the Spring container, I can annotate the method that defines the Student class bean in the ApplicationConfiguration class as follows:
1 2 3 4 5 |
@Bean @ConditionalOnBean(Clazz.class) public Student student() { return new Student("Khanh"); } |
In case the Spring container has not yet defined a bean for the Clazz class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.huongdanjava.springboot.springbootconditionalonbean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @Configuration public class ApplicationConfiguration { @Bean @ConditionalOnBean(Clazz.class) public Student student() { return new Student("Khanh"); } } |
then you will see the following error when running again for example:
If you define a bean for the Clazz class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.huongdanjava.springboot.springbootconditionalonbean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; @Configuration public class ApplicationConfiguration { @Bean public Clazz clazz() { return new Clazz("A"); } @Bean @ConditionalOnBean(Clazz.class) public Student student() { return new Student("Khanh"); } } |
then you won’t see the error anymore.
If you take a look at the content of the @ConditionalOnBean annotation, you will see that this annotation uses the @Conditional annotation along with the implementation of the Condition interface, OnBeanCondition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnBean { Class<?>[] value() default {}; String[] type() default {}; Class<? extends Annotation>[] annotation() default {}; String[] name() default {}; SearchStrategy search() default SearchStrategy.ALL; Class<?>[] parameterizedContainer() default {}; } |
You can read the code of the OnBeanCondition class if you want to learn more about how Spring Boot implements this condition!
As you can see, in addition to using the class name to declare in the @ConditionalOnBean annotation, we can also use a number of other ways to declare conditions. For example, we can use the name of the bean in the Spring container to declare it in the name attribute of the @ConditionalOnBean annotation as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.huongdanjava.springboot.springbootconditionalonbean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; @Configuration public class ApplicationConfiguration { @Bean public Clazz clazz() { return new Clazz("A"); } @Bean @ConditionalOnBean(name = {"clazz"}) public Student student() { return new Student("Khanh"); } } |