Skip to content

Spring Boot @ConditionalOnProperty Annotation

The Smart Home Appliance Analogy

Imagine purchasing a luxury modern home equipped with smart appliances. In the ceiling sits an automated climate control system. The system contains circuitry for both an electric heat pump and a traditional natural gas furnace. However, during installation, the electrical inspector checks the basement utility connection. If the house has a natural gas line hooked up and the breaker switch labeled utilities.gas.enabled is switched to ON, the system registers the gas furnace bean and ignites gas heat. If the switch is OFF or no gas line is connected, the furnace bean is never loaded, preventing gas leaks and error codes.

In Spring Boot, @ConditionalOnProperty is that automated breaker switch. In modern enterprise applications, you often build features that should only exist under specific configuration conditions: a third party payment integration, a scheduled maintenance job, an internal metrics exporter, or an audit listener. Instead of instantiating beans that fail at runtime because their API keys are missing, @ConditionalOnProperty tells Spring to inspect application.properties during startup and conditionally register the bean only if a specified property matches your criteria.

This lecture covers the mechanics of @ConditionalOnProperty, its core attributes (name, havingValue, matchIfMissing), building dynamic feature flags, and handling fallback bean registration.


Why Conditional Bean Registration Matters

Without conditional configuration, applications must handle missing dependencies through complex branching logic inside methods:

java
// Anti-pattern: The bean is always instantiated even when feature is disabled
@Service
public class SmsNotificationService {

    @Value("${notification.sms.enabled:false}")
    private boolean enabled;

    public void sendSms(String phone, String message) {
        if (!enabled) {
            // Waste of memory and initialization overhead
            return;
        }
        // Send SMS logic...
    }
}

This anti pattern has serious flaws:

  1. The SmsNotificationService is always instantiated, consuming memory.
  2. If its constructor requires an SMS gateway API client, that client must be instantiated, failing if credentials are absent.
  3. Background scheduled tasks inside the bean still trigger unless guarded by repeated checks.

With @ConditionalOnProperty, Spring never creates the bean if the property is disabled:

java
@Service
@ConditionalOnProperty(name = "notification.sms.enabled", havingValue = "true")
public class SmsNotificationService implements NotificationService {
    // Only created if notification.sms.enabled=true
}

Core Attributes of @ConditionalOnProperty

The annotation provides five attributes:

AttributeTypeDescription
prefixStringCommon prefix applied to property names (e.g. app.features)
name / valueString[]The specific property key or keys to evaluate
havingValueStringThe string value the property must match to activate the bean
matchIfMissingbooleanIf true, the bean is created even when the property is completely absent
java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
    String[] value() default {};
    String prefix() default "";
    String[] name() default {};
    String havingValue() default "";
    boolean matchIfMissing() default false;
}

Understanding havingValue and matchIfMissing

The combination of havingValue and matchIfMissing dictates exactly how Spring evaluates condition logic:

Case 1: Standard Feature Flag (havingValue = "true", matchIfMissing = false)

java
@Bean
@ConditionalOnProperty(
    prefix = "feature.export",
    name = "pdf-enabled",
    havingValue = "true",
    matchIfMissing = false // default
)
public PdfExportService pdfExportService() {
    return new PdfExportService();
}

Behavior:

  • Property is feature.export.pdf-enabled=true -> Bean Loaded
  • Property is feature.export.pdf-enabled=false -> Bean Skipped
  • Property is absent from configuration file -> Bean Skipped

Case 2: Opt Out Feature (matchIfMissing = true)

You want a feature to be active by default unless the developer explicitly turns it off:

java
@Bean
@ConditionalOnProperty(
    prefix = "app.cache",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true // Active by default!
)
public CacheManager cacheManager() {
    return new RedisCacheManager();
}

Behavior:

  • Property is omitted entirely -> Bean Loaded (because matchIfMissing=true)
  • Property is app.cache.enabled=true -> Bean Loaded
  • Property is app.cache.enabled=false -> Bean Skipped

Case 3: Boolean Truthiness Without havingValue

If you omit havingValue, Spring checks whether the property is present and not equal to "false":

java
@Bean
@ConditionalOnProperty(name = "analytics.tracker.enabled")
public Tracker analyticsTracker() {
    return new AnalyticsTracker();
}

Behavior:

  • Property exists and is anything other than "false" -> Bean Loaded
  • Property is analytics.tracker.enabled=false -> Bean Skipped
  • Property is absent -> Bean Skipped

Pattern: Primary and Fallback Beans

You can combine @ConditionalOnProperty with fallback beans so your application always has an active implementation:

java
package com.example.orderservice.service;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

public interface FileStorageService {
    void store(String filename, byte[] data);
}

// Production AWS S3 Storage: active only when storage.type=s3
@Service
@ConditionalOnProperty(name = "storage.type", havingValue = "s3")
public class S3FileStorageService implements FileStorageService {
    public S3FileStorageService() {
        System.out.println("Initialized AWS S3 Storage Client");
    }

    @Override
    public void store(String filename, byte[] data) {
        System.out.println("Uploading " + filename + " to Amazon S3 Bucket");
    }
}

// Local Filesystem Storage: active only when storage.type=local (or default)
@Service
@ConditionalOnProperty(name = "storage.type", havingValue = "local", matchIfMissing = true)
public class LocalFileStorageService implements FileStorageService {
    public LocalFileStorageService() {
        System.out.println("Initialized Local Disk Storage Directory");
    }

    @Override
    public void store(String filename, byte[] data) {
        System.out.println("Writing " + filename + " to local /tmp directory");
    }
}

In application.properties:

  • If you set storage.type=s3, only S3FileStorageService is registered.
  • If you set storage.type=local or omit the property, only LocalFileStorageService is registered.
  • There is zero bean collision, and downstream services can safely inject FileStorageService with zero qualifiers.

Interview Questions & Pitfalls

Q1: What is the primary purpose of @ConditionalOnProperty in Spring Boot?

@ConditionalOnProperty enables conditional bean registration. It instructs Spring to inspect configuration properties at startup and instantiate a bean only if the specified property exists, matches a designated havingValue, or satisfies matchIfMissing rules.

Q2: What happens if matchIfMissing is set to true and the property is absent?

Spring treats the missing property as a condition match and instantiates the bean. This is the standard pattern for creating opt out features that should remain active by default unless explicitly disabled by setting the property to false.

Q3: Can @ConditionalOnProperty be applied to both classes and @Bean methods?

Yes. Placing it on a @Service or @Component class controls whether the entire class is registered. Placing it on a @Bean method inside a @Configuration class controls whether that specific bean definition method executes.

Q4: How does relaxed binding apply to property names in @ConditionalOnProperty?

@ConditionalOnProperty supports relaxed binding. Writing name = "max-threads" matches max-threads, maxThreads, max_threads, and environment variable MAX_THREADS.

Q5: What is the difference between @Profile and @ConditionalOnProperty?

@Profile is a broad, environment level coarse switch (e.g. dev, prod) that activates entire groups of files and beans. @ConditionalOnProperty is a granular, property specific feature flag that inspects specific key value configurations regardless of which overall environment profile is active.