Conditional Beans
Conditional beans let the container decide whether to register a bean based on the runtime environment: which properties are set, which classes are on the classpath, which beans already exist, or which profile is active. This is the mechanism that makes Spring Boot’s auto-configuration intelligent and overridable. This page covers the conditional annotations you will use most.
Why conditions matter
Without conditions, every @Bean method runs unconditionally. That is fine for application code, but it breaks down for reusable libraries and auto-configuration, where a bean should only appear when it makes sense — for example, a DataSource only when a JDBC driver is present, or a caching bean only when caching is enabled.
Conditions evaluate at context startup, before the bean is instantiated. If a condition fails, the bean definition is skipped entirely as if it were never declared.
Note: The
@ConditionalOn*annotations live inspring-boot-autoconfigure(packageorg.springframework.boot.autoconfigure.condition), which every Spring Boot starter pulls in transitively. They are not part of core Spring Framework — that core only ships the lower-level@Conditionaland theConditioninterface.
@ConditionalOnProperty
Registers a bean only when a configuration property has a given value. This is the most common switch for feature flags.
@Configuration
public class NotificationConfig {
@Bean
@ConditionalOnProperty(
prefix = "app.notifications",
name = "channel",
havingValue = "email")
public NotificationSender emailSender() {
return new EmailNotificationSender();
}
@Bean
@ConditionalOnProperty(
prefix = "app.notifications",
name = "channel",
havingValue = "sms")
public NotificationSender smsSender() {
return new SmsNotificationSender();
}
}
app:
notifications:
channel: email # only emailSender() is registered
Use matchIfMissing = true when a bean should be active by default (i.e. when the property is absent):
@Bean
@ConditionalOnProperty(
prefix = "app.cache",
name = "enabled",
havingValue = "true",
matchIfMissing = true)
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
@ConditionalOnClass and @ConditionalOnMissingClass
Registers a bean only when a type is (or is not) present on the classpath. Auto-configuration uses this constantly to react to which libraries you added.
@Configuration
@ConditionalOnClass(name = "com.zaxxer.hikari.HikariDataSource")
public class HikariConfig {
@Bean
public DataSourceTuner dataSourceTuner() {
return new DataSourceTuner();
}
}
Referencing the class by string name (name = "...") avoids a NoClassDefFoundError when the type is genuinely absent. You can also reference it directly with value = HikariDataSource.class when you are sure the import resolves at compile time.
@ConditionalOnMissingBean
Registers a bean only when no bean of that type already exists. This is the key to making library beans overridable: the library supplies a sensible default, but if you define your own bean, yours wins.
@Configuration
public class JsonConfig {
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
return new ObjectMapper().findAndRegisterModules();
}
}
Tip: Ordering matters. Auto-configuration classes are processed after your application’s own
@Configuration, so a user-definedObjectMapperis already present by the time@ConditionalOnMissingBeanevaluates — and the default is skipped. This is why you can override almost any Spring Boot default simply by declaring your own bean.
@Profile
@Profile is a coarser conditional: it activates beans only when one or more named profiles are active. Profiles are ideal for environment-specific wiring (dev vs prod), while @ConditionalOnProperty is better for fine-grained feature toggles.
@Configuration
public class DataSourceConfig {
@Bean
@Profile("dev")
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Profile("prod")
public DataSource pooledDataSource(DataSourceProperties props) {
return props.initializeDataSourceBuilder().build();
}
}
You can negate a profile with @Profile("!prod") and combine expressions like @Profile("prod & cloud").
Comparing the conditions
| Annotation | Activates when | Typical use |
|---|---|---|
@ConditionalOnProperty | A property has a value (or is missing) | Feature flags, opt-in/opt-out |
@ConditionalOnClass | A type is on the classpath | React to an added dependency |
@ConditionalOnMissingClass | A type is absent | Fallback when a library is missing |
@ConditionalOnMissingBean | No matching bean exists yet | Overridable library defaults |
@ConditionalOnBean | A matching bean already exists | Wire only when a prerequisite is present |
@Profile | A named profile is active | Environment-specific beans |
How auto-configuration uses them
A typical Spring Boot auto-configuration class layers several conditions so it activates only in exactly the right circumstances:
@AutoConfiguration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
public class MyDataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
}
Read together: the class only applies when a DataSource type is on the classpath and a spring.datasource.url is configured, and even then the DataSource bean only appears if the user has not defined one. To see which conditions matched or were rejected at startup, run with --debug to print the condition evaluation report — covered in Auto-Configuration.
Warning: A bean’s conditions can depend on other beans defined in the same configuration class only if those are evaluated first. Avoid circular condition dependencies; if you need ordering, split beans across separate auto-configuration classes and use
@AutoConfigureBefore/@AutoConfigureAfter.
Best Practices
- Reach for
@ConditionalOnPropertyfor feature toggles and@Profilefor whole-environment switches — don’t conflate the two. - Pair
@ConditionalOnMissingBeanwith library defaults so consumers can override without forking your code. - Reference optional types by string
namein@ConditionalOnClassto stay safe when the class is absent. - Keep conditions on
@Configuration/@AutoConfigurationclasses coarse and conditions on individual@Beanmethods fine-grained. - Verify your conditions with the
--debugevaluation report rather than guessing why a bean did or did not appear.