@Conditional… in Spring Boot

While building Spring Boot app, we could fall in one use case where we need to load some of our beans conditionally i.e. Loading that bean based on another bean.

Spring 4.0 has introduced a new annotation @Conditional that allows us to use either pre-defined or custom conditions which will be applied to bean in application context.

In this blog, we will introduce you with some of the @Conditional annotation with pre-defined and custom conditions.

Why we need Conditional Beans

We can have certain requirement to load some of the beans based on either some other bean, class or configuration. As an example we can take use case of Application Security beans. While development in local machine, we don’t want developer to put username/password every time to test the same API. So we can disable those bean’s initialization locally and it should work in higher environments.

@Conditional

If we add a condition to a single @Bean definition, this bean is only loaded if the condition is met i.e.

@Configuration
class ApplicationConfiguration {

  @Bean
  @Conditional(...some condition...)
  public MyBean getMyBean(){
    return new MyBean();
  };
}

If we add a @Conditional to a Spring @Configuration class then all the beans declared in that configuration class will be loaded if the condition is met. i.e.

@Configuration
@Conditional(...some condition...)
class ApplicationConfiguration {
  
  @Bean
  public MyBean getMyBean(){
    return new MyBean();
  };

}

We can add @Conditional to any bean declared with any of the annotations like @Component@Service@Repository, or @Controller i.e.

@Component
@Conditional(...some condition...)
class ConditionalComponent{
...
}

@Conditional… with pre-defined conditions

@ConditionalOnProperty

The @ConditionalOnProperty annotation allows to load beans conditionally depending on a certain configurational property. i.e.

    @Bean
    @ConditionalOnProperty(
            value = "emp.address.enabled",
            havingValue = "true",
            matchIfMissing = false
    )
    public Address getAddress(){
        return new Address();
    }

Above bean will only be loaded if in config some where we have emp.address.enabled flag as true. matchIfMissing says, if not config found then set true by default.

@ConditionalOnExpression

    @Bean
    @ConditionalOnExpression(
            "${module.emp.enabled} and ${module.address.enabled:true}"
    )
    public Employee getEmployee(){
        return new Employee();
    }

Employee bean will be loaded if expressions module.emp.enabled and module.address.enabled is found true. Note, module.address.enabled is set with default value as true.

@ConditionalOnBean

    @Bean
    @ConditionalOnBean(Address.class)
    public Employee getEmployeeWithAddress(){
        return new Employee();
    }

Employee bean will be build if Address bean class is there in application context. We can also put bean name instead of bean class

@ConditionalOnMissingBean

    @Bean
    @ConditionalOnMissingBean(
            NotABean.class
    )
    public Name getName(){
        return new Name();
    }

Name bean will be loaded if NotABean bean class is not found in application context or classpath.

@ConditionalOnResource

    @Bean
    @ConditionalOnResource(
            resources = "/application.properties"
    )
    public NoResourceBean getNoResourceBean(){
        return new NoResourceBean();
    }

The above bean will only be loaded if we have application.properties in our classpath.

@ConditionalOnClass

@Bean
@ConditionalOnClass(
  name = "org.springframework.boot.web.embedded.tomcat.TomcatWebServer")
public ClassPathBean getClassPathBean(){
  return new ClassPathBean();
}

ClassPathBean will be loaded only if class TomcatWebServer is found in classpath.

@ConditionalOnMissingClass

    @Bean
    @ConditionalOnMissingClass(
            value = "class.not.found"
    )
    public MissingClassBean getMissingClassBean(){
        return new MissingClassBean();
    }

MissingClassBean will be loaded only if given class in value attribute is not found in classpath.

@ConditionalOnJndi

    @Bean
    @ConditionalOnJndi(
            value = "java:comp/env/datasource"
    )
    public JNDIClass getJNDIClass(){
        return new JNDIClass();
    }

Load the bean only if certain JNDI resource is found in JNDI config.

@ConditionalOnJava

    @Bean
    @ConditionalOnJava(
            value = JavaVersion.EIGHT
    )
    public JavaBean getJavaBean(){
        return new JavaBean();
    }

JavaBean will be loaded only if the running Java version in 8.

@ConditionalOnSingleCandidate

    @Bean
    @ConditionalOnSingleCandidate(
            value = Employee.class
    )
    public SingleCandidateBean getSingleCandidateBean(){
        return new SingleCandidateBean();
    }

SingleCandidateBean will be loaded only if one instance of Employee bean in found in application context.

@ConditionalOnWebApplication

    @Bean
    @ConditionalOnWebApplication
    public WebApplicationBean getWebApplicationBean(){
        return new WebApplicationBean();
    }

Bean will be loaded only if our application is running in web application container.

@ConditionalOnNotWebApplication

    @Bean
    @ConditionalOnNotWebApplication
    public NotWebApplication getNotWebApplication(){
        return new NotWebApplication();
    }

Custom Conditional Conditions:

We may need to create our own custom condition if all above pre-defined conditions are not actually fits in our requirement.

Custom condition can be created by simple implementing Spring’s Condition interface like below.

public class CustomCondition implements Condition {

    @Override
    public boolean matches(
            ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        ConditionBean conditionBean = (ConditionBean) context.getBeanFactory().getBean("ConditionBean");
        return conditionBean != null;
    }
}

If you can see what matches method is doing is just giving us True/False based on some conditions. In our case we are checking whether the sample bean ConditionBean is present in application context or not.

Then we can use the above CustomCondition condition any where like below.

    @Bean
    @Conditional(CustomCondition.class)
    public CustomConditionBean getCustomConditionBean(){
        return new CustomConditionBean();
    }

Combining Multiple Conditions with OR

If we want to combine multiple conditions into a single condition with the logical “OR” operator, we can extend Spring’s AnyNestedCondition class like below.

public class AppNestedCondition extends AnyNestedCondition {

    public AppNestedCondition() {
        super(ConfigurationPhase.REGISTER_BEAN);
    }

    @Conditional(CustomConditionFirst.class)
    static class OnCustomConditionFirst {
    }

    @Conditional(CustomConditionSecond.class)
    static class OnCustomConditionSecond {
    }
}

Of course you need to have CustomConditionFirst and CustomConditionSecond conditions ready.

Above AppNestedCondition can be used like below.

    @Bean
    @Conditional(AppNestedCondition.class)
    public NestedConditionBean getNestedConditionBean(){
        return new NestedConditionBean();
    }

Combining Multiple Conditions with AND

If we want to combine multiple conditions with “AND” logic, we can simply use multiple @Conditional… annotations on a single bean. They will automatically be combined with the logical “AND” operator so that if at least one condition fails, the bean will not be loaded. Or if we want to combine conditions with AND into a single @Conditional annotation, we can extend Spring’s AllNestedConditions class

The @Conditional annotation cannot be used more than once on a single method or class.

Combining Conditions with NOT

Similar to AnyNestedCondition or AllNestedConditions, we can extend NoneNestedCondition to only load beans if NONE of the combined conditions match.

Defining a Custom @ConditionalOn… Annotation

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(AppNestedCondition.class)
public @interface ConditionalApp {
}

Above custom annotation can be used like below

    @Bean
    @ConditionalApp
    //@Conditional(AppNestedCondition.class)
    public NestedConditionBean getNestedConditionBean(){
        return new NestedConditionBean();
    }

So all the above pre-defined or custom conditions can be used with @Conditional annotation as per your use case.

Hope this article must have cleared your doubt about Spring’s @Condtitional… annotations.

The above code snippets can be found as application on GitHub

Advertisements

One thought on “@Conditional… in Spring Boot

Comments are closed.