0
# Runtime Lookup Conditional Beans
1
2
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.
3
4
## Capabilities
5
6
### LookupIfProperty Annotation
7
8
Makes beans available for programmatic lookup when runtime properties match specified values.
9
10
```java { .api }
11
/**
12
* Indicates that a bean should only be obtained by programmatic lookup if the property matches the provided value.
13
* This annotation is repeatable. A bean will be included if all the conditions defined by the LookupIfProperty and
14
* LookupUnlessProperty annotations are satisfied.
15
*/
16
@Repeatable(LookupIfProperty.List.class)
17
@Retention(RetentionPolicy.RUNTIME)
18
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD })
19
public @interface LookupIfProperty {
20
/**
21
* Name of the runtime property to check
22
*/
23
String name();
24
25
/**
26
* Expected String value of the runtime property (specified by name) if the bean should be looked up at runtime.
27
*/
28
String stringValue();
29
30
/**
31
* Determines if the bean is to be looked up when the property name specified by name has not been specified at all
32
*/
33
boolean lookupIfMissing() default false;
34
35
@Retention(RetentionPolicy.RUNTIME)
36
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD })
37
@interface List {
38
LookupIfProperty[] value();
39
}
40
}
41
```
42
43
**Usage Examples:**
44
45
```java
46
import jakarta.enterprise.context.ApplicationScoped;
47
import jakarta.enterprise.inject.Instance;
48
import jakarta.inject.Inject;
49
import io.quarkus.arc.lookup.LookupIfProperty;
50
51
interface Service {
52
String name();
53
}
54
55
// Service available for lookup only when property matches
56
@LookupIfProperty(name = "service.foo.enabled", stringValue = "true")
57
@ApplicationScoped
58
class ServiceFoo implements Service {
59
public String name() {
60
return "foo";
61
}
62
}
63
64
// Always available service
65
@ApplicationScoped
66
class ServiceBar implements Service {
67
public String name() {
68
return "bar";
69
}
70
}
71
72
@ApplicationScoped
73
class Client {
74
75
@Inject
76
Instance<Service> services;
77
78
public void printServiceNames() {
79
// This will include ServiceFoo only if service.foo.enabled=true
80
services.stream()
81
.forEach(service -> System.out.println(service.name()));
82
83
// Direct lookup with fallback handling
84
if (!services.isUnsatisfied()) {
85
Service service = services.get();
86
System.out.println("Selected: " + service.name());
87
}
88
}
89
}
90
```
91
92
### LookupUnlessProperty Annotation
93
94
Makes beans available for programmatic lookup when runtime properties do NOT match specified values.
95
96
```java { .api }
97
/**
98
* Indicates that a bean should only be obtained by programmatic lookup if the property does not match the provided value.
99
* This annotation is repeatable. A bean will be included if all the conditions defined by the LookupUnlessProperty
100
* and LookupIfProperty annotations are satisfied.
101
*/
102
@Repeatable(LookupUnlessProperty.List.class)
103
@Retention(RetentionPolicy.RUNTIME)
104
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD })
105
public @interface LookupUnlessProperty {
106
/**
107
* Name of the runtime property to check
108
*/
109
String name();
110
111
/**
112
* Expected String value of the runtime property (specified by name) if the bean should be skipped at runtime.
113
*/
114
String stringValue();
115
116
/**
117
* Determines if the bean should be looked up when the property name specified by name has not been specified at all
118
*/
119
boolean lookupIfMissing() default false;
120
121
@Retention(RetentionPolicy.RUNTIME)
122
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD })
123
@interface List {
124
LookupUnlessProperty[] value();
125
}
126
}
127
```
128
129
**Usage Examples:**
130
131
```java
132
import jakarta.enterprise.context.ApplicationScoped;
133
import jakarta.enterprise.inject.Instance;
134
import jakarta.inject.Inject;
135
import io.quarkus.arc.lookup.LookupUnlessProperty;
136
137
interface ProcessingService {
138
void process(String data);
139
}
140
141
// Available for lookup unless disabled
142
@LookupUnlessProperty(name = "service.premium.disabled", stringValue = "true")
143
@ApplicationScoped
144
class PremiumProcessingService implements ProcessingService {
145
public void process(String data) {
146
System.out.println("Premium processing of: " + data);
147
}
148
}
149
150
// Always available fallback
151
@ApplicationScoped
152
class StandardProcessingService implements ProcessingService {
153
public void process(String data) {
154
System.out.println("Standard processing of: " + data);
155
}
156
}
157
158
@ApplicationScoped
159
class ProcessingManager {
160
161
@Inject
162
Instance<ProcessingService> processors;
163
164
public void handleData(String data) {
165
// Select first available processor
166
ProcessingService processor = processors.iterator().next();
167
processor.process(data);
168
}
169
}
170
```
171
172
### Multiple Conditions
173
174
Combine multiple lookup conditions for sophisticated runtime selection logic.
175
176
```java
177
import jakarta.enterprise.context.ApplicationScoped;
178
import jakarta.enterprise.inject.Instance;
179
import jakarta.inject.Inject;
180
import io.quarkus.arc.lookup.LookupIfProperty;
181
import io.quarkus.arc.lookup.LookupUnlessProperty;
182
183
interface NotificationHandler {
184
void handle(String message);
185
}
186
187
// Available only when both conditions are met
188
@ApplicationScoped
189
@LookupIfProperty(name = "notifications.email.enabled", stringValue = "true")
190
@LookupUnlessProperty(name = "notifications.email.maintenance", stringValue = "true")
191
class EmailNotificationHandler implements NotificationHandler {
192
public void handle(String message) {
193
System.out.println("Email: " + message);
194
}
195
}
196
197
// Available when Slack is enabled
198
@ApplicationScoped
199
@LookupIfProperty(name = "notifications.slack.enabled", stringValue = "true")
200
class SlackNotificationHandler implements NotificationHandler {
201
public void handle(String message) {
202
System.out.println("Slack: " + message);
203
}
204
}
205
206
// Fallback handler - always available
207
@ApplicationScoped
208
class LogNotificationHandler implements NotificationHandler {
209
public void handle(String message) {
210
System.out.println("Log: " + message);
211
}
212
}
213
214
@ApplicationScoped
215
class NotificationService {
216
217
@Inject
218
Instance<NotificationHandler> handlers;
219
220
public void broadcast(String message) {
221
// Send via all available handlers
222
handlers.stream()
223
.forEach(handler -> handler.handle(message));
224
}
225
226
public void sendViaPreferred(String message) {
227
// Send via first available handler (based on lookup conditions)
228
if (!handlers.isUnsatisfied()) {
229
handlers.get().handle(message);
230
}
231
}
232
}
233
```
234
235
### Producer Method Usage
236
237
Lookup conditions work with producer methods for dynamic bean creation.
238
239
```java
240
import jakarta.enterprise.context.ApplicationScoped;
241
import jakarta.enterprise.inject.Produces;
242
import jakarta.enterprise.inject.Instance;
243
import jakarta.inject.Inject;
244
import io.quarkus.arc.lookup.LookupIfProperty;
245
246
@ApplicationScoped
247
public class DatabaseConnectionProducer {
248
249
@Produces
250
@LookupIfProperty(name = "database.pool.enabled", stringValue = "true")
251
public DatabaseConnection pooledConnection() {
252
return new PooledDatabaseConnection();
253
}
254
255
@Produces
256
@LookupUnlessProperty(name = "database.pool.enabled", stringValue = "true")
257
public DatabaseConnection simpleConnection() {
258
return new SimpleDatabaseConnection();
259
}
260
}
261
262
@ApplicationScoped
263
class DatabaseService {
264
265
@Inject
266
Instance<DatabaseConnection> connections;
267
268
public void performQuery(String sql) {
269
// Use the appropriate connection based on runtime configuration
270
DatabaseConnection connection = connections.get();
271
connection.execute(sql);
272
}
273
}
274
275
interface DatabaseConnection {
276
void execute(String sql);
277
}
278
279
class PooledDatabaseConnection implements DatabaseConnection {
280
public void execute(String sql) {
281
System.out.println("Executing via pool: " + sql);
282
}
283
}
284
285
class SimpleDatabaseConnection implements DatabaseConnection {
286
public void execute(String sql) {
287
System.out.println("Executing directly: " + sql);
288
}
289
}
290
```
291
292
### Advanced Selection Patterns
293
294
Implement complex runtime selection logic with multiple conditions.
295
296
```java
297
import jakarta.enterprise.context.ApplicationScoped;
298
import jakarta.enterprise.inject.Instance;
299
import jakarta.inject.Inject;
300
import io.quarkus.arc.lookup.LookupIfProperty;
301
import io.quarkus.arc.lookup.LookupUnlessProperty;
302
303
interface PaymentProcessor {
304
String getName();
305
void processPayment(double amount);
306
}
307
308
// Production Stripe processor
309
@ApplicationScoped
310
@LookupIfProperty(name = "payment.provider", stringValue = "stripe")
311
@LookupUnlessProperty(name = "payment.test-mode", stringValue = "true")
312
class StripeProductionProcessor implements PaymentProcessor {
313
public String getName() { return "Stripe Production"; }
314
public void processPayment(double amount) {
315
System.out.println("Processing $" + amount + " via Stripe Production");
316
}
317
}
318
319
// Test Stripe processor
320
@ApplicationScoped
321
@LookupIfProperty(name = "payment.provider", stringValue = "stripe")
322
@LookupIfProperty(name = "payment.test-mode", stringValue = "true")
323
class StripeTestProcessor implements PaymentProcessor {
324
public String getName() { return "Stripe Test"; }
325
public void processPayment(double amount) {
326
System.out.println("Processing $" + amount + " via Stripe Test");
327
}
328
}
329
330
// PayPal processor
331
@ApplicationScoped
332
@LookupIfProperty(name = "payment.provider", stringValue = "paypal")
333
class PayPalProcessor implements PaymentProcessor {
334
public String getName() { return "PayPal"; }
335
public void processPayment(double amount) {
336
System.out.println("Processing $" + amount + " via PayPal");
337
}
338
}
339
340
// Mock processor for development
341
@ApplicationScoped
342
@LookupIfProperty(name = "payment.provider", stringValue = "mock")
343
class MockPaymentProcessor implements PaymentProcessor {
344
public String getName() { return "Mock"; }
345
public void processPayment(double amount) {
346
System.out.println("MOCK: Processing $" + amount);
347
}
348
}
349
350
@ApplicationScoped
351
class PaymentService {
352
353
@Inject
354
Instance<PaymentProcessor> processors;
355
356
public void listAvailableProcessors() {
357
System.out.println("Available payment processors:");
358
processors.stream()
359
.forEach(processor -> System.out.println("- " + processor.getName()));
360
}
361
362
public void processPayment(double amount) {
363
if (processors.isUnsatisfied()) {
364
throw new IllegalStateException("No payment processor available");
365
}
366
367
// Use the first available processor
368
PaymentProcessor processor = processors.get();
369
processor.processPayment(amount);
370
}
371
372
public PaymentProcessor selectProcessor(String preferredName) {
373
return processors.stream()
374
.filter(processor -> processor.getName().contains(preferredName))
375
.findFirst()
376
.orElse(processors.get());
377
}
378
}
379
```
380
381
## Runtime Property Configuration
382
383
Runtime properties are evaluated when beans are looked up, not at build time:
384
385
### Application Properties
386
387
```properties
388
# These are evaluated at runtime
389
service.foo.enabled=true
390
notifications.email.enabled=true
391
notifications.slack.enabled=false
392
payment.provider=stripe
393
payment.test-mode=false
394
```
395
396
### Dynamic Configuration
397
398
```java
399
// Properties can change at runtime through configuration sources
400
@ApplicationScoped
401
class DynamicConfigExample {
402
403
@ConfigProperty(name = "service.foo.enabled")
404
String serviceFooEnabled;
405
406
public void toggleService() {
407
// Changing this property affects future Instance<T> lookups
408
System.setProperty("service.foo.enabled",
409
"true".equals(serviceFooEnabled) ? "false" : "true");
410
}
411
}
412
```
413
414
## Benefits and Use Cases
415
416
### Performance Benefits
417
- Beans are always created at build time, avoiding runtime conditional creation overhead
418
- Only lookup filtering happens at runtime, which is lightweight
419
- Avoids `@ConditionalOnProperty` runtime overhead
420
421
### Dynamic Behavior
422
- Enable/disable features at runtime without application restart
423
- A/B testing and feature toggles
424
- Environment-specific behavior in same deployment
425
- Integration enabling/disabling based on runtime conditions