Spring Boot’s conditional beans let you surgically inject components into your application context only when specific criteria are met, but most developers don’t realize these conditions can be chained to create incredibly granular and dynamic application configurations.
Let’s see this in action. Imagine we have a NotificationService that can be either an EmailService or an SmsService. We want to activate the correct one based on a property in application.properties.
First, define the interfaces and implementations:
public interface NotificationService {
void sendNotification(String message);
}
@Service
public class EmailService implements NotificationService {
@Override
public void sendNotification(String message) {
System.out.println("Sending email: " + message);
}
}
@Service
public class SmsService implements NotificationService {
@Override
public void sendNotification(String message) {
System.out.println("Sending SMS: " + message);
}
}
Now, let’s make them conditional:
@Configuration
public class NotificationConfig {
@Bean
@ConditionalOnProperty(name = "notification.service.type", havingValue = "email")
public NotificationService emailNotificationService() {
return new EmailService();
}
@Bean
@ConditionalOnProperty(name = "notification.service.type", havingValue = "sms")
public NotificationService smsNotificationService() {
return new SmsService();
}
}
And our application.properties:
notification.service.type=email
When Spring Boot starts, it scans NotificationConfig. It sees the @Bean methods for emailNotificationService and smsNotificationService.
For emailNotificationService, it checks notification.service.type in application.properties. Since it’s email, the condition havingValue = "email" is met, and this EmailService bean is created and added to the application context.
For smsNotificationService, it checks the same property. notification.service.type is email, which does not match havingValue = "sms", so this bean is not created.
In our main application class, we can now autowire NotificationService, and Spring will provide the correct implementation:
@SpringBootApplication
public class ConditionalBeansApp {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ConditionalBeansApp.class, args);
NotificationService notificationService = context.getBean(NotificationService.class);
notificationService.sendNotification("Hello from Spring Boot!");
}
}
If you run this with notification.service.type=email, you’ll see "Sending email: Hello from Spring Boot!". Change it to sms in application.properties and rerun, and you’ll see "Sending SMS: Hello from Spring Boot!".
The problem this solves is managing configurations that vary based on environment, deployment targets, or feature flags without resorting to cumbersome if/else blocks in your main application logic or multiple application contexts. You can define different beans for the same interface, and Spring Boot’s conditions ensure only the appropriate one is active.
Internally, Spring’s ConfigurationCondition interface is what powers these annotations. When Spring encounters a @Conditional annotation on a @Bean method or a @Configuration class, it delegates to a specific Condition implementation (like OnPropertyCondition for @ConditionalOnProperty). This condition is evaluated during the bean definition phase. If the condition evaluates to false, the bean definition (or the entire @Configuration class) is effectively ignored for that application context.
You can also combine conditions. For instance, to activate a bean only if a specific class is present and a property is set:
@Bean
@ConditionalOnClass(SomeExternalLibrary.class)
@ConditionalOnProperty(name = "feature.x.enabled", havingValue = "true")
public MyFeatureService myFeatureService() {
return new MyFeatureServiceImpl();
}
This MyFeatureService will only be instantiated if SomeExternalLibrary is on the classpath and the feature.x.enabled property is set to true in your properties.
What most people miss is how @ConditionalOnMissingBean and @ConditionalOnBean interact with the order of bean definition. If you define multiple beans for the same type, @ConditionalOnMissingBean will pick the first one that Spring encounters and registers. This means the order of your @Configuration classes and the order of @Bean methods within a configuration class can implicitly determine which bean wins if you’re relying on OnMissingBean. It’s not just about the condition itself, but the discovery and registration order of beans that can satisfy a missing bean requirement.
The next logical step is exploring how to create custom @Conditional annotations for more complex, application-specific requirements.