Build time CDI dependency injection framework for Quarkus applications with conditional bean support and context management
—
Control which beans are available for programmatic lookup based on runtime properties, enabling dynamic behavior without bean activation overhead. These annotations work with Instance<T> for conditional bean resolution at runtime.
Makes beans available for programmatic lookup when runtime properties match specified values.
/**
* Indicates that a bean should only be obtained by programmatic lookup if the property matches the provided value.
* This annotation is repeatable. A bean will be included if all the conditions defined by the LookupIfProperty and
* LookupUnlessProperty annotations are satisfied.
*/
@Repeatable(LookupIfProperty.List.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD })
public @interface LookupIfProperty {
/**
* Name of the runtime property to check
*/
String name();
/**
* Expected String value of the runtime property (specified by name) if the bean should be looked up at runtime.
*/
String stringValue();
/**
* Determines if the bean is to be looked up when the property name specified by name has not been specified at all
*/
boolean lookupIfMissing() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD })
@interface List {
LookupIfProperty[] value();
}
}Usage Examples:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import io.quarkus.arc.lookup.LookupIfProperty;
interface Service {
String name();
}
// Service available for lookup only when property matches
@LookupIfProperty(name = "service.foo.enabled", stringValue = "true")
@ApplicationScoped
class ServiceFoo implements Service {
public String name() {
return "foo";
}
}
// Always available service
@ApplicationScoped
class ServiceBar implements Service {
public String name() {
return "bar";
}
}
@ApplicationScoped
class Client {
@Inject
Instance<Service> services;
public void printServiceNames() {
// This will include ServiceFoo only if service.foo.enabled=true
services.stream()
.forEach(service -> System.out.println(service.name()));
// Direct lookup with fallback handling
if (!services.isUnsatisfied()) {
Service service = services.get();
System.out.println("Selected: " + service.name());
}
}
}Makes beans available for programmatic lookup when runtime properties do NOT match specified values.
/**
* Indicates that a bean should only be obtained by programmatic lookup if the property does not match the provided value.
* This annotation is repeatable. A bean will be included if all the conditions defined by the LookupUnlessProperty
* and LookupIfProperty annotations are satisfied.
*/
@Repeatable(LookupUnlessProperty.List.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD })
public @interface LookupUnlessProperty {
/**
* Name of the runtime property to check
*/
String name();
/**
* Expected String value of the runtime property (specified by name) if the bean should be skipped at runtime.
*/
String stringValue();
/**
* Determines if the bean should be looked up when the property name specified by name has not been specified at all
*/
boolean lookupIfMissing() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD })
@interface List {
LookupUnlessProperty[] value();
}
}Usage Examples:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import io.quarkus.arc.lookup.LookupUnlessProperty;
interface ProcessingService {
void process(String data);
}
// Available for lookup unless disabled
@LookupUnlessProperty(name = "service.premium.disabled", stringValue = "true")
@ApplicationScoped
class PremiumProcessingService implements ProcessingService {
public void process(String data) {
System.out.println("Premium processing of: " + data);
}
}
// Always available fallback
@ApplicationScoped
class StandardProcessingService implements ProcessingService {
public void process(String data) {
System.out.println("Standard processing of: " + data);
}
}
@ApplicationScoped
class ProcessingManager {
@Inject
Instance<ProcessingService> processors;
public void handleData(String data) {
// Select first available processor
ProcessingService processor = processors.iterator().next();
processor.process(data);
}
}Combine multiple lookup conditions for sophisticated runtime selection logic.
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import io.quarkus.arc.lookup.LookupIfProperty;
import io.quarkus.arc.lookup.LookupUnlessProperty;
interface NotificationHandler {
void handle(String message);
}
// Available only when both conditions are met
@ApplicationScoped
@LookupIfProperty(name = "notifications.email.enabled", stringValue = "true")
@LookupUnlessProperty(name = "notifications.email.maintenance", stringValue = "true")
class EmailNotificationHandler implements NotificationHandler {
public void handle(String message) {
System.out.println("Email: " + message);
}
}
// Available when Slack is enabled
@ApplicationScoped
@LookupIfProperty(name = "notifications.slack.enabled", stringValue = "true")
class SlackNotificationHandler implements NotificationHandler {
public void handle(String message) {
System.out.println("Slack: " + message);
}
}
// Fallback handler - always available
@ApplicationScoped
class LogNotificationHandler implements NotificationHandler {
public void handle(String message) {
System.out.println("Log: " + message);
}
}
@ApplicationScoped
class NotificationService {
@Inject
Instance<NotificationHandler> handlers;
public void broadcast(String message) {
// Send via all available handlers
handlers.stream()
.forEach(handler -> handler.handle(message));
}
public void sendViaPreferred(String message) {
// Send via first available handler (based on lookup conditions)
if (!handlers.isUnsatisfied()) {
handlers.get().handle(message);
}
}
}Lookup conditions work with producer methods for dynamic bean creation.
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import io.quarkus.arc.lookup.LookupIfProperty;
@ApplicationScoped
public class DatabaseConnectionProducer {
@Produces
@LookupIfProperty(name = "database.pool.enabled", stringValue = "true")
public DatabaseConnection pooledConnection() {
return new PooledDatabaseConnection();
}
@Produces
@LookupUnlessProperty(name = "database.pool.enabled", stringValue = "true")
public DatabaseConnection simpleConnection() {
return new SimpleDatabaseConnection();
}
}
@ApplicationScoped
class DatabaseService {
@Inject
Instance<DatabaseConnection> connections;
public void performQuery(String sql) {
// Use the appropriate connection based on runtime configuration
DatabaseConnection connection = connections.get();
connection.execute(sql);
}
}
interface DatabaseConnection {
void execute(String sql);
}
class PooledDatabaseConnection implements DatabaseConnection {
public void execute(String sql) {
System.out.println("Executing via pool: " + sql);
}
}
class SimpleDatabaseConnection implements DatabaseConnection {
public void execute(String sql) {
System.out.println("Executing directly: " + sql);
}
}Implement complex runtime selection logic with multiple conditions.
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import io.quarkus.arc.lookup.LookupIfProperty;
import io.quarkus.arc.lookup.LookupUnlessProperty;
interface PaymentProcessor {
String getName();
void processPayment(double amount);
}
// Production Stripe processor
@ApplicationScoped
@LookupIfProperty(name = "payment.provider", stringValue = "stripe")
@LookupUnlessProperty(name = "payment.test-mode", stringValue = "true")
class StripeProductionProcessor implements PaymentProcessor {
public String getName() { return "Stripe Production"; }
public void processPayment(double amount) {
System.out.println("Processing $" + amount + " via Stripe Production");
}
}
// Test Stripe processor
@ApplicationScoped
@LookupIfProperty(name = "payment.provider", stringValue = "stripe")
@LookupIfProperty(name = "payment.test-mode", stringValue = "true")
class StripeTestProcessor implements PaymentProcessor {
public String getName() { return "Stripe Test"; }
public void processPayment(double amount) {
System.out.println("Processing $" + amount + " via Stripe Test");
}
}
// PayPal processor
@ApplicationScoped
@LookupIfProperty(name = "payment.provider", stringValue = "paypal")
class PayPalProcessor implements PaymentProcessor {
public String getName() { return "PayPal"; }
public void processPayment(double amount) {
System.out.println("Processing $" + amount + " via PayPal");
}
}
// Mock processor for development
@ApplicationScoped
@LookupIfProperty(name = "payment.provider", stringValue = "mock")
class MockPaymentProcessor implements PaymentProcessor {
public String getName() { return "Mock"; }
public void processPayment(double amount) {
System.out.println("MOCK: Processing $" + amount);
}
}
@ApplicationScoped
class PaymentService {
@Inject
Instance<PaymentProcessor> processors;
public void listAvailableProcessors() {
System.out.println("Available payment processors:");
processors.stream()
.forEach(processor -> System.out.println("- " + processor.getName()));
}
public void processPayment(double amount) {
if (processors.isUnsatisfied()) {
throw new IllegalStateException("No payment processor available");
}
// Use the first available processor
PaymentProcessor processor = processors.get();
processor.processPayment(amount);
}
public PaymentProcessor selectProcessor(String preferredName) {
return processors.stream()
.filter(processor -> processor.getName().contains(preferredName))
.findFirst()
.orElse(processors.get());
}
}Runtime properties are evaluated when beans are looked up, not at build time:
# These are evaluated at runtime
service.foo.enabled=true
notifications.email.enabled=true
notifications.slack.enabled=false
payment.provider=stripe
payment.test-mode=false// Properties can change at runtime through configuration sources
@ApplicationScoped
class DynamicConfigExample {
@ConfigProperty(name = "service.foo.enabled")
String serviceFooEnabled;
public void toggleService() {
// Changing this property affects future Instance<T> lookups
System.setProperty("service.foo.enabled",
"true".equals(serviceFooEnabled) ? "false" : "true");
}
}@ConditionalOnProperty runtime overheadInstall with Tessl CLI
npx tessl i tessl/maven-io-quarkus--quarkus-arc