Question: What are conditional annotations in Spring Boot?
Answer: Conditional annotations tell Spring, “Create this bean or configuration only if a certain condition is true.” In Spring Boot, they are a big part of auto-configuration, which means Boot can safely add features only when the right classes, properties, or beans are present. A simple example is loading one service when a property is set, and a fallback service when it is not.
Interview-Ready Answer: I think of conditional annotations as feature gates for Spring beans. They let Spring decide at startup whether a bean should exist based on things like a property value, a class on the classpath, or whether another bean is already present. Spring Boot uses them heavily in auto-configuration so it can add sensible defaults without overriding my app’s own beans. The most common examples are @ConditionalOnProperty, @ConditionalOnClass, and @ConditionalOnMissingBean.
Detailed Explanation: A conditional annotation is an annotation that works with Spring’s @Conditional mechanism. A Condition is a rule object: if the rule matches, Spring registers the bean or configuration; if it does not, Spring skips it. In Spring Boot, the common versions live under org.springframework.boot.autoconfigure.condition and are designed for auto-configuration.
Condition rule that checks the environment, the classpath, or the bean registry.ConditionEvaluationReport, which is why running with --debug can show you why something was or was not created.@ConditionalOnProperty: turn features on or off with config flags such as app.payments.enabled=true.@ConditionalOnClass: only load a bean if a library is present, which is useful for optional integrations.@ConditionalOnMissingBean: provide a safe default only when the user has not defined their own bean.@ConditionalOnBean: create a bean only after another required bean exists.@Profile: choose beans by environment such as dev or prod; this is Spring-wide, not Boot-only, and is less fine-grained than the Boot conditions above.| Annotation | Best for | Common gotcha |
|---|---|---|
@ConditionalOnProperty | Feature flags | Missing or unexpected values skip the bean |
@ConditionalOnClass | Optional libraries | The class must truly be on the classpath |
@ConditionalOnMissingBean | Safe defaults | Order matters in auto-config |
@Profile | Environment-based grouping | Too coarse for feature toggles |
These checks are usually very cheap, because Spring Boot tries to evaluate them from metadata instead of eagerly loading classes. The cost is tiny compared with creating the bean itself, so the main benefit is avoiding work. A useful mental model is: conditions are a doorway check, not a full security scan.
Two important edge cases interviewers like:
havingValue must match the property value you expect; a typo means the bean disappears.@ConditionalOnMissingBean is great for defaults, but if you use it carelessly you can accidentally hide a user’s custom bean.Memory Hook: Imagine Spring as a nightclub bouncer: each conditional annotation is a guest list rule. If the name, class, or ticket is right, the bean gets in; otherwise, it waits outside.
Real-World Story: In a checkout service, you may want Stripe support only if the Stripe SDK is present and the flag payments.stripe.enabled=true is set. If the SDK is missing, Spring skips the Stripe bean and the app can still start with another provider or a safe fallback.
What goes wrong when people misunderstand this? A team ships a release with a property typo such as payments.stripe.enbled=true. Spring never creates the Stripe bean, the payment flow falls back unexpectedly, and support sees user complaints like “cards are not being charged through the new provider.” In logs, you might see a missing-bean message or the auto-configuration report showing that the condition did not match. The symptom is often not a crash; it is a feature silently not activating, which makes conditional annotations powerful but also easy to misconfigure.
package com.example.conditionalannotationsdemo;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@SpringBootApplication
public class ConditionalAnnotationsDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ConditionalAnnotationsDemoApplication.class, args);
}
// Use ObjectProvider so the demo can report a missing bean cleanly
// instead of failing startup. In real apps, this is a good way to
// handle optional dependencies or to show a helpful error message.
@Bean
CommandLineRunner runner(ObjectProvider<ShippingService> shippingServiceProvider, Environment environment) {
return args -> {
String configuredMode = environment.getProperty("app.shipping", "<not set>");
ShippingService service = shippingServiceProvider.getIfAvailable();
System.out.println("app.shipping = " + configuredMode);
if (service == null) {
// This is the edge case: a typo or unexpected value means no bean matched.
System.out.println("No ShippingService bean was created.");
System.out.println("Use app.shipping=express or leave it unset to get the default service.");
return;
}
System.out.println("Active service: " + service.name());
System.out.println("Quote for 3 items: " + service.quote(3));
};
}
interface ShippingService {
String name();
int quote(int itemCount);
}
@Configuration(proxyBeanMethods = false)
static class ExpressShippingConfiguration {
@Bean
@ConditionalOnProperty(prefix = "app", name = "shipping", havingValue = "express")
ShippingService expressShippingService() {
return new ShippingService() {
@Override
public String name() {
return "Express Shipping";
}
@Override
public int quote(int itemCount) {
return 25 + (itemCount * 3);
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class StandardShippingConfiguration {
@Bean
@ConditionalOnProperty(prefix = "app", name = "shipping", havingValue = "standard", matchIfMissing = true)
ShippingService standardShippingService() {
return new ShippingService() {
@Override
public String name() {
return "Standard Shipping";
}
@Override
public int quote(int itemCount) {
return 8 + (itemCount * 1);
}
};
}
}
}
/*
Run examples:
1) Default behavior: no property set
-> Standard Shipping is created because matchIfMissing = true.
2) Feature enabled:
java -jar app.jar --app.shipping=express
-> Express Shipping is created.
3) Failure path / typo:
java -jar app.jar --app.shipping=expres
-> No bean matches, so the runner prints a helpful message.
Why this teaches the concept:
- @ConditionalOnProperty acts like a feature flag.
- matchIfMissing shows the safe default pattern.
- The failure path proves that conditions decide bean creation at startup.
*/Follow-up & Tricky Questions:
@ConditionalOnMissingBean so common in auto-configuration? It lets Boot provide a default implementation without overriding the application’s own bean, which is the core of Boot’s “opinionated but overridable” design.@Profile over conditional annotations? Use @Profile for broad environment splits like dev/test/prod. Use Boot conditionals when the decision depends on a property, a classpath library, or another bean.--debug or inspect the condition evaluation report. That tells you which condition failed and usually points straight to a wrong property or missing class.@ConditionalOnProperty create the bean when the property key is misspelled? No. A misspelled key is effectively missing, so the condition will not match unless you explicitly set matchIfMissing = true.@ConditionalOnClass checking whether the bean exists? No, it checks whether a class is available on the classpath. That is different from bean presence and is used to guard optional dependencies.@ConditionalOnExpression? Usually no. It is available, but it is harder to read and maintain than a simple property or bean-based condition, and it should not replace normal application logic.Common Mistakes:
@ConditionalOnMissingBean is always safe. Fix: use it only for true defaults and keep the auto-configuration order in mind.@ConditionalOnClass asks, “Is the library present?” not “Did Spring create a bean?”Memory Hook: “If the ticket is valid, Spring lets the bean in.” Think of conditional annotations as a door guard for beans: property, class, or existing-bean checks decide who enters.
Cheat Sheet:
@ConditionalOnProperty = feature flag.@ConditionalOnClass = optional library.@ConditionalOnMissingBean = safe default.--debug to see why a condition matched or failed.Practice Tasks:
pickup, and switch between three implementations.@ConditionalOnMissingBean and observe how a user-defined bean overrides it.