CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkus--quarkus-arc

Build time CDI dependency injection framework for Quarkus applications with conditional bean support and context management

Pending
Overview
Eval results
Files

runtime-lookup.mddocs/

Runtime Lookup Conditional Beans

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.

Capabilities

LookupIfProperty Annotation

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());
        }
    }
}

LookupUnlessProperty Annotation

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);
    }
}

Multiple Conditions

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);
        }
    }
}

Producer Method Usage

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);
    }
}

Advanced Selection Patterns

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 Property Configuration

Runtime properties are evaluated when beans are looked up, not at build time:

Application Properties

# These are evaluated at runtime
service.foo.enabled=true
notifications.email.enabled=true
notifications.slack.enabled=false
payment.provider=stripe
payment.test-mode=false

Dynamic Configuration

// 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");
    }
}

Benefits and Use Cases

Performance Benefits

  • Beans are always created at build time, avoiding runtime conditional creation overhead
  • Only lookup filtering happens at runtime, which is lightweight
  • Avoids @ConditionalOnProperty runtime overhead

Dynamic Behavior

  • Enable/disable features at runtime without application restart
  • A/B testing and feature toggles
  • Environment-specific behavior in same deployment
  • Integration enabling/disabling based on runtime conditions

Install with Tessl CLI

npx tessl i tessl/maven-io-quarkus--quarkus-arc

docs

bean-container.md

bean-invocation.md

build-profiles.md

build-properties.md

index.md

interceptor-integration.md

logger-injection.md

runtime-lookup.md

tile.json