Careeroza
Conditional Annotations
The Common Conditions
| Annotation | Bean is created when... |
|---|---|
@ConditionalOnClass(X.class) | class X is on the classpath |
@ConditionalOnMissingClass("x.Y") | class is NOT on the classpath |
@ConditionalOnBean(X.class) | a bean of type X already exists |
@ConditionalOnMissingBean | no bean of this type exists yet (lets users override) |
@ConditionalOnProperty(name="x", havingValue="true") | a property has a given value |
@ConditionalOnWebApplication | the app is a web application |
@Profile("prod") | the profile is active |
Choosing a Payment Gateway by Property
public interface PaymentGateway {
String charge(String orderId, BigDecimal amount);
}
@Configuration
public class PaymentConfig {
@Bean
@ConditionalOnProperty(name = "shopease.payment.provider", havingValue = "razorpay")
PaymentGateway razorpayGateway() { return new RazorpayGateway(); }
@Bean
@ConditionalOnProperty(name = "shopease.payment.provider", havingValue = "stripe")
PaymentGateway stripeGateway() { return new StripeGateway(); }
@Bean
@ConditionalOnProperty(name = "shopease.payment.provider", havingValue = "fake", matchIfMissing = true)
PaymentGateway fakeGateway() { return new FakeGateway(); } // local development
}Note: @ConditionalOnMissingBean only sees beans registered before it, so it is reliable in auto-configuration classes (which run after your own configuration) but risky between beans of the same @Configuration class. Property-based conditions, as above, are safer there.
How Conditions Are Evaluated
Example
A plain-Java model: each bean definition carries a condition that is checked before the bean is created:
import java.util.*;
import java.util.function.*;
public class Main {
public static void main(String[] args) {
Map<String, String> props = Map.of("shopease.payment.provider", "razorpay");
Set<String> classpath = Set.of("com.zaxxer.hikari.HikariDataSource");
List<Definition> defs = List.of(
new Definition("hikariDataSource", ctx -> classpath.contains("com.zaxxer.hikari.HikariDataSource")),
new Definition("mongoClient", ctx -> classpath.contains("com.mongodb.client.MongoClient")),
new Definition("razorpayGateway", ctx -> "razorpay".equals(props.get("shopease.payment.provider"))),
new Definition("stripeGateway", ctx -> "stripe".equals(props.get("shopease.payment.provider"))),
new Definition("fakeGateway", ctx -> !ctx.contains("razorpayGateway") && !ctx.contains("stripeGateway"))
);
Set<String> context = new LinkedHashSet<>();
for (Definition d : defs) {
boolean ok = d.condition().test(context);
System.out.printf("%-17s %s%n", d.name(), ok ? "CREATED" : "skipped");
if (ok) context.add(d.name());
}
System.out.println("Beans in context: " + context);
}
}
record Definition(String name, Predicate<Set<String>> condition) {}Output
hikariDataSource CREATED mongoClient skipped razorpayGateway CREATED stripeGateway skipped fakeGateway skipped Beans in context: [hikariDataSource, razorpayGateway]
Practice
Exercise: Create a SmsSender bean only when shopease.sms.enabled=true, treating a missing property as false.
Show answer
@Bean
@ConditionalOnProperty(name = "shopease.sms.enabled", havingValue = "true", matchIfMissing = false)
SmsSender smsSender() {
return new Msg91SmsSender();
}