Conditions — The Switches Behind Auto-configuration
library-service should send overdue-book reminder emails in production, but developers do not want real emails leaving their laptops. You could wrap the code in if statements, but Spring has a cleaner tool, the same one Boot uses for all its auto-configuration: conditional beans.
The rule
A condition is a question asked at startup; the bean exists only if the answer is yes.
Each @ConditionalOn... annotation asks one question. The common ones:
| Annotation | The question |
|---|---|
@ConditionalOnClass(X.class) |
Is class X on the classpath? |
@ConditionalOnMissingClass("x.Y") |
Is class Y absent? |
@ConditionalOnBean(X.class) |
Has a bean of type X already been defined? |
@ConditionalOnMissingBean(X.class) |
Is there no bean of type X yet? |
@ConditionalOnProperty(...) |
Does a property have a given value? |
@ConditionalOnWebApplication |
Is this a web app? |
Put one on a @Bean method and it guards that bean. Put one on a configuration class and it guards every bean inside. Several conditions on the same element must all be true.
Here is the reminder switch for library-service:
@Configuration
public class ReminderConfig {
@Bean
@ConditionalOnProperty(name = "library.reminders.enabled", havingValue = "true")
public ReminderSender emailReminders(JavaMailSender mail) {
return new EmailReminderSender(mail);
}
@Bean
@ConditionalOnProperty(name = "library.reminders.enabled", havingValue = "false",
matchIfMissing = true)
public ReminderSender loggingReminders() {
return new LoggingReminderSender(); // just writes to the log
}
}In production, library.reminders.enabled=true creates the email sender. On a laptop the property is absent: the first condition fails, because by default @ConditionalOnProperty treats a missing property as "no", while the second passes thanks to matchIfMissing = true. Exactly one ReminderSender exists in each environment.
A word of care about @ConditionalOnMissingBean: it only sees beans that were registered before it is checked. That is safe in auto-configuration, which always runs after your own configuration. Inside your own classes the registration order is not guaranteed, so prefer property or class conditions there and leave "missing bean" checks to auto-configuration.
The shortcut
Read any conditional bean as a sentence: "Create this bean only if ___." Fill the blank from each annotation and join them with "and".
The annotations
A bean has
@ConditionalOnClass(ObjectMapper.class)and@ConditionalOnMissingBean.
Drills
Answer fast — trust the rule.
Which condition creates a bean only when a library is present?
A bean has @ConditionalOnProperty(name = "library.reminders.enabled", havingValue = "true") and the property is not set at all. Is it created?
A bean carries both @ConditionalOnClass(DataSource.class) and @ConditionalOnProperty(name = "x", havingValue = "on"). When is it created?
Why does @ConditionalOnMissingBean work reliably in Boot's auto-configuration?
Remember
- A condition is a question asked at startup; the bean exists only if the answer is yes.
- Read every conditional bean as "create this only if ___ and ___."
- Trap: a missing property counts as "no" unless you set
matchIfMissing = true.
