Generic Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we'll take a look at the @Conditional annotation. It's used to indicate whether a given component is eligible for registration based on a defined condition.

We'll learn how to use predefined conditional annotations, combine them with different conditions as well as create our custom, condition-based annotations.

2. Declaring Conditions

Before we move into the implementation, let's firstly see in which situations we could make use of conditional annotations.

The most common usage would be to include or exclude the whole configuration class:

@Configuration
@Conditional(IsDevEnvCondition.class)
class DevEnvLoggingConfiguration {
    // ...
}

Or just a single bean:

@Configuration
class DevEnvLoggingConfiguration {
    
    @Bean
    @Conditional(IsDevEnvCondition.class)
    LoggingService loggingService() {
        return new LoggingService();
    }
}

By doing so, we can base the behavior of our application on given conditions. For instance, the type of environment or specific needs of our clients. In the above example, we initialize additional logging services only for the development environment.

Another way of making the component conditional would be to place the condition directly on the component class:

@Service
@Conditional(IsDevEnvCondition.class)
class LoggingService {
    // ...
}

We can apply the above example to any bean declared with the @Component@Service@Repository, or @Controller annotations.

3. Predefined Conditional Annotations

Spring comes with a set of predefined conditional annotations. Let's go through some of the most popular ones.

Firstly, let's see how we can base a component on a configuration property value:

@Service
@ConditionalOnProperty(
  value="logging.enabled", 
  havingValue = "true", 
  matchIfMissing = true)
class LoggingService {
    // ...
}

The first attribute, value, tells us what configuration property we'll be looking at. The second one, havingValue, defines a value that's required for this condition. And lastly, the matchIfMissing attribute tells the Spring whether the condition should be matched if the parameter is missing.

Similarly, we can base the condition on an expression:

@Service
@ConditionalOnExpression(
  "${logging.enabled:true} and '${logging.level}'.equals('DEBUG')"
)
class LoggingService {
    // ...
}

Now, Spring will create the LoggingService only when both the logging.enabled configuration property is set to true, and the logging.level is set to DEBUG.

Another condition we can apply is to check whether a given bean was created:

@Service
@ConditionalOnBean(CustomLoggingConfiguration.class)
class LoggingService {
    // ...
}

Or a given class exists on the classpath:

@Service
@ConditionalOnClass(CustomLogger.class)
class LoggingService {
    // ...
}

We can achieve the opposite behavior by applying the @ConditionalOnMissingBean or @ConditionalOnMissingClass annotations.

In addition, we can depend our components on a given Java version:

@Service
@ConditionalOnJava(JavaVersion.EIGHT)
class LoggingService {
    // ...
}

In the above example, the LoggingService will be created only when the runtime environment is Java 8.

Finally, we can use the @ConditionalOnWarDeployment annotation to enable bean only in war packaging:

@Configuration
@ConditionalOnWarDeployment
class AdditionalWebConfiguration {
    // ...
}

Note, that for applications with embedded servers, this condition will return false.

4. Defining Custom Conditions

Spring allows us to customize the behavior of the @Conditional annotation by creating our custom condition templates. To create one, we simply need to implement the Condition interface:

class Java8Condition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return JavaVersion.getJavaVersion().equals(JavaVersion.EIGHT);
    }
}

The matches method tells Spring whether the condition has passed or not. It has two arguments that give us respectively information about the context where the bean will initialize and the metadata of the used @Conditional annotation.

As we can see, in our example, we just check if the Java version is 8.

After that, we should place our new condition as an attribute in the @Conditional annotation:

@Service
@Conditional(Java8Condition.class)
public class Java8DependedService {
    // ...
}

This way, the Java8DependentService will be created only where the condition from Java8Condition class is matched.

5. Combining Conditions

For more complex solutions, we can group conditional annotations with OR or AND logical operators.

To apply the OR operator, we need to create a custom condition extending the AnyNestedCondition class. Inside it, we need to create an empty static class for each condition and annotate it with a proper @Conditional implementation.

For example, let's create a condition that requires either Java 8 or Java 9:

class Java8OrJava9 extends AnyNestedCondition {
    
    Java8OrJava9() {
        super(ConfigurationPhase.REGISTER_BEAN);
    }
    
    @Conditional(Java8Condition.class)
    static class Java8 { }
    
    @Conditional(Java9Condition.class)
    static class Java9 { }
    
}

The AND operator, on the other hand, is much simpler. We can simply group the conditions:

@Service
@Conditional({IsWindowsCondition.class, Java8Condition.class})
@ConditionalOnJava(JavaVersion.EIGHT)
public class LoggingService {
    // ...
}

In the above example, the LoggingService will be created only when both the IsWindowsCondition and Java8Condition are matched.

6. Summary

In this article, we've learned how to use and create conditional annotations. As always, all the source code is available over on GitHub.

Generic bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
Comments are closed on this article!