@Condition Annotation
In modern Spring development, we frequently need to dynamically decide whether to register a specific bean based on different runtime environments or configurations. The @Conditional annotation was designed precisely for this purpose. Introduced in Spring 4.0, it empowers developers to control bean registration according to custom criteria, serving as the foundational cornerstone of Spring Boot's auto-configuration mechanism.
Placed on either classes or methods, the @Conditional annotation evaluates specific rules before instantiation. Its core contract can be summarized simply: a annotated configuration class or bean is registered into the Spring container only when all specified conditions are met. Common real-world use cases include:
- Registering different bean implementations based on the operating system type;
- Enabling specific features only when a particular class is present on the classpath;
- Switching implementation classes according to values in configuration properties;
- Creating default beans exclusively when no such bean currently exists in the container.
At the heart of@Conditionallies theConditioninterface. Developers define custom assembly logic by implementing itsmatches()method:
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
The two parameters serve distinct purposes:
ConditionContext: Grants the ability to access environment properties, the class loader, theBeanFactory, and theBeanDefinitionRegistry.AnnotatedTypeMetadata: Provides metadata access to other annotations on the annotated class or method.
A Practical Example of Custom Conditions
To illustrate the complete workflow, let us look at an example that injects different services depending on the operating system.
Step 1: Define the Condition Classes
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String os = context.getEnvironment().getProperty("os.name");
return os != null && os.toLowerCase().contains("windows");
}
}
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String os = context.getEnvironment().getProperty("os.name");
return os != null && os.toLowerCase().contains("linux");
}
}
Step 2: Apply the Conditions in a Configuration Class
@Configuration
public class ListServiceConfig {
@Bean
@Conditional(WindowsCondition.class)
public ListService windowsListService() {
return new WindowsListService();
}
@Bean
@Conditional(LinuxCondition.class)
public ListService linuxListService() {
return new LinuxListService();
}
}
At runtime, Spring evaluates these conditions sequentially. Only the @Bean method whose condition matches successfully will take effect, ensuring that exactly one ListService instance ends up in the container.
Advanced Tip: You can combine @Conditional with other annotations to create semantic, custom annotations:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(WindowsCondition.class)
public @interface ConditionalOnWindows {
}
Using @ConditionalOnWindows directly afterward significantly enhances code readability.
Derivative Annotations in Spring Boot
Building upon @Conditional, Spring Boot provides a rich set of out-of-the-box conditional annotations that drive its auto-configuration engine:
| Annotation | Activation Condition |
|---|---|
@ConditionalOnClass |
The specified class is present on the classpath |
@ConditionalOnMissingClass |
The specified class is missing from the classpath |
@ConditionalOnBean |
The specified bean already exists in the container |
@ConditionalOnMissingBean |
The specified bean does not exist in the container |
@ConditionalOnProperty |
Configuration properties meet specific criteria |
@ConditionalOnWebApplication |
The current application is a web application |
@ConditionalOnExpression |
The SpEL expression evaluates to true |
For instance, a typical auto-configuration pattern looks like this:
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
return new HikariDataSource();
}
}
This configuration loads only when the DataSource class is available on the classpath, and it supplies a default implementation solely when the user has not defined their own DataSource. This perfectly embodies the "convention over configuration" philosophy.
Best Practices and Considerations
When utilizing conditional annotations in production environments, keep the following guidelines in mind:
- Condition Evaluation Order: Annotations like
@ConditionalOnBeanrely on the order in which beans are registered. Because the loading order of ordinary configuration classes cannot be strictly guaranteed, these annotations should primarily be reserved for auto-configuration classes. - Avoid Condition Conflicts: Multiple conditional annotations on the same target function with an implicit "AND" logic—all conditions must pass to take effect. Design your conditional hierarchies with this combined logic in mind.
- Keep Condition Classes Lightweight: The
matches()method executes during the container startup phase. Avoid performing heavy or time-consuming operations inside it. - Leverage Debugging Tools: Setting
debug=truein your properties or startup configuration prompts Spring Boot to print a comprehensiveConditionEvaluationReportin the logs, making it much easier to troubleshoot why a particular bean failed to register.
Mastering the @Conditional annotation not only helps you decode the source code of Spring Boot's auto-configuration mechanisms, but also equips you to build highly flexible and elegant component assembly logic for complex, multi-environment business scenarios.