@ConditionalOnProperty

Registers a bean or configuration only when a configuration property is present, or has a specific value, so features can be switched per environment.

Signature
@ConditionalOnProperty(prefix = "app.audit", name = "enabled", havingValue = "true", matchIfMissing = false)
  • Added in Spring Boot Spring Boot 1.1.0

Parameters

ParameterTypeDescription
$value / nameoptionalString[]= {}Property names to test (with prefix prepended). All must match. Use value or name, not both.
$prefixoptionalString= ""Prefix applied to every name, for example app.audit.
$havingValueoptionalString= ""Expected value, compared case-insensitively. When empty, the condition matches if the property exists and is not "false".
$matchIfMissingoptionalboolean= falseWhether the condition matches when the property is not set at all.

Return value

n/aRegisters the annotated configuration class, component or @Bean method only when the named property matches.

Errors

Nothing is thrown when the condition does not match: the bean is skipped. Any injection point that requires it then fails startup with NoSuchBeanDefinitionException.

Changelog

  • PHP Spring Boot 3.5.0The annotation became repeatable, and @ConditionalOnBooleanProperty was added for boolean flags.

@ConditionalOnProperty decides at startup whether a bean exists, based on a property. It is how Spring Boot's own auto-configuration offers enabled switches, and it works the same on your @Configuration classes, @Bean methods and components.

@Bean
@ConditionalOnProperty(prefix = "app.audit", name = "enabled", havingValue = "true")
AuditPublisher auditPublisher() { ... }

How matching works

  • With havingValue set, the property must equal it (ignoring case).
  • Without havingValue, the property must exist and must not be false. Any other value, even no or 0, matches.
  • matchIfMissing decides what happens when the property is absent. The default false means a feature is off until switched on; true means it is on unless switched off.
  • Several names must all match.

Spring Boot 3.5 added @ConditionalOnBooleanProperty, whose havingValue is a boolean. For on/off flags it reads better and avoids the "no counts as on" surprise. The same release made @ConditionalOnProperty repeatable, so several properties with different expected values can be required on one bean.

Properties versus profiles

A profile describes an environment; a property describes a feature. app.audit.enabled=true can be set in exactly the environments that need it, overridden with APP_AUDIT_ENABLED, and read by anyone without knowing what each profile means. Profile-specific files are then a good place to set those properties.

Mistakes to avoid

  • Choosing the wrong default. For something dangerous (deleting old data, sending real SMS) leave matchIfMissing false so a forgotten setting means "off".
  • Making a bean conditional while other beans require it. When the property is off, startup fails with NoSuchBeanDefinitionException. Inject ObjectProvider<T>, or provide a no-op bean for the "off" case with the opposite condition.
  • Expecting it to react at runtime. The condition is evaluated once, when the context starts; changing the property later has no effect until a restart.

The conditions report printed with debug=true lists each conditional bean and why it matched or not, which is the quickest way to see why a bean is missing.

Example

Example · java
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;

interface AuditPublisher {
    void publish(String event);
}

class KafkaAuditPublisher implements AuditPublisher {
    public void publish(String event) {
        // send to the audit topic
    }
}

class NoOpAuditPublisher implements AuditPublisher {
    public void publish(String event) {
    }
}

@Configuration(proxyBeanMethods = false)
class AuditConfig {

    @Bean
    @ConditionalOnProperty(prefix = "app.audit", name = "enabled", havingValue = "true")
    AuditPublisher kafkaAuditPublisher() {
        return new KafkaAuditPublisher();
    }

    // The "off" case still provides a bean, so callers never break.
    @Bean
    @ConditionalOnProperty(prefix = "app.audit", name = "enabled", havingValue = "false", matchIfMissing = true)
    AuditPublisher noOpAuditPublisher() {
        return new NoOpAuditPublisher();
    }
}

@Service
public class AccountAuditService {

    private final AuditPublisher audit;

    public AccountAuditService(AuditPublisher audit) {
        this.audit = audit;
    }

    public void passwordChanged(long userId) {
        audit.publish("password-changed:" + userId);
    }
}

When to use it

  • Switching an audit publisher on in production while development uses a no-op
  • Switching a scheduled cleanup job off in the multi-replica API deployment so only a single worker deployment runs it
  • Shipping a feature dark and enabling it per environment without a new build

Frequently asked questions

What does matchIfMissing do?

It decides the result when the property is not set at all. false (the default) means the bean is skipped; true means it is registered unless the property says otherwise.

Why is my conditional bean created even though I set the property to no?

Without havingValue, any value other than false matches. Set havingValue = "true" or use @ConditionalOnBooleanProperty.

Should I use @ConditionalOnProperty or @Profile?

Use a property for a feature that can be on or off anywhere, and a profile for differences between environments. Profiles can set the properties.

More examples

A boolean flag that is on unless switched off

The job runs unless app.cleanup.enabled=false is set. Replicas of one deployment share the same environment, so a flag cannot single out one replica: switch the job off in the API deployment and run it in a separate single-instance worker, or keep it on everywhere behind a distributed lock. @ConditionalOnBooleanProperty only matches the values true and false; anything else counts as not matching.

Example · java
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Configuration(proxyBeanMethods = false)
@EnableScheduling
@ConditionalOnBooleanProperty(name = "app.cleanup.enabled", matchIfMissing = true)
public class CleanupConfig {
}

@Component
@ConditionalOnBooleanProperty(name = "app.cleanup.enabled", matchIfMissing = true)
class ExpiredSessionCleaner {

    @Scheduled(cron = "0 15 3 * * *")
    void purge() {
        // delete sessions that expired more than a day ago
    }
}

Setting the switches per environment

The defaults live in the main file and production switches the audit feature on. Environment variables override both, so a single deployment can be adjusted without a rebuild.

Example · properties
# application.properties: safe defaults
app.audit.enabled=false
app.cleanup.enabled=true

# application-prod.properties would contain:
#   app.audit.enabled=true
# and the API deployment, which runs several replicas, switches the job off
# so only the single-instance worker deployment runs it:
#   APP_CLEANUP_ENABLED=false

Testing both sides of the condition

ApplicationContextRunner starts only the configuration under test, so each case runs in milliseconds. The third test pins down that havingValue = "true" rejects other spellings.

Example · java
import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

class SmsSender {
}

@Configuration(proxyBeanMethods = false)
class SmsConfig {

    @Bean
    @ConditionalOnProperty(name = "app.sms.enabled", havingValue = "true")
    SmsSender smsSender() {
        return new SmsSender();
    }
}

class SmsConfigTest {

    private final ApplicationContextRunner runner =
            new ApplicationContextRunner().withUserConfiguration(SmsConfig.class);

    @Test
    void offByDefault() {
        runner.run(context -> assertThat(context).doesNotHaveBean(SmsSender.class));
    }

    @Test
    void onWhenEnabled() {
        runner.withPropertyValues("app.sms.enabled=true")
              .run(context -> assertThat(context).hasSingleBean(SmsSender.class));
    }

    @Test
    void anyOtherValueIsOff() {
        runner.withPropertyValues("app.sms.enabled=yes")
              .run(context -> assertThat(context).doesNotHaveBean(SmsSender.class));
    }
}

Discussion

  • Be the first to comment on this lesson.