Skip to content
Spring Boot sb core 4 min read

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 in spring-boot-autoconfigure (package org.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 @Conditional and the Condition interface.

@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-defined ObjectMapper is already present by the time @ConditionalOnMissingBean evaluates — 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

AnnotationActivates whenTypical use
@ConditionalOnPropertyA property has a value (or is missing)Feature flags, opt-in/opt-out
@ConditionalOnClassA type is on the classpathReact to an added dependency
@ConditionalOnMissingClassA type is absentFallback when a library is missing
@ConditionalOnMissingBeanNo matching bean exists yetOverridable library defaults
@ConditionalOnBeanA matching bean already existsWire only when a prerequisite is present
@ProfileA named profile is activeEnvironment-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 @ConditionalOnProperty for feature toggles and @Profile for whole-environment switches — don’t conflate the two.
  • Pair @ConditionalOnMissingBean with library defaults so consumers can override without forking your code.
  • Reference optional types by string name in @ConditionalOnClass to stay safe when the class is absent.
  • Keep conditions on @Configuration/@AutoConfiguration classes coarse and conditions on individual @Bean methods fine-grained.
  • Verify your conditions with the --debug evaluation report rather than guessing why a bean did or did not appear.
Last updated June 13, 2026
Was this helpful?