0
# Build-Time Processing
1
2
The Build-Time Processing capability provides the recorder system for build-time code generation and bytecode manipulation in Quarkus applications.
3
4
## Recorder System
5
6
### Recorder Annotation
7
8
```java { .api }
9
@Retention(RetentionPolicy.RUNTIME)
10
@Target(ElementType.TYPE)
11
public @interface Recorder {
12
// Marks classes as build-time recorders for bytecode generation
13
}
14
```
15
16
### Initialization Phase Annotations
17
18
```java { .api }
19
@Retention(RetentionPolicy.RUNTIME)
20
@Target(ElementType.METHOD)
21
public @interface StaticInit {
22
// Marks recorder methods for static initialization phase (build-time)
23
}
24
25
@Retention(RetentionPolicy.RUNTIME)
26
@Target(ElementType.METHOD)
27
public @interface RuntimeInit {
28
// Marks recorder methods for runtime initialization phase (startup)
29
}
30
31
@Retention(RetentionPolicy.RUNTIME)
32
@Target(ElementType.TYPE)
33
public @interface StaticInitSafe {
34
// Marks classes as safe for static initialization
35
}
36
```
37
38
### Constructor Annotation
39
40
```java { .api }
41
@Retention(RetentionPolicy.RUNTIME)
42
@Target(ElementType.CONSTRUCTOR)
43
public @interface RecordableConstructor {
44
// Marks constructors as recordable for bytecode generation
45
}
46
```
47
48
### Main Method Annotation
49
50
```java { .api }
51
@Retention(RetentionPolicy.RUNTIME)
52
@Target(ElementType.TYPE)
53
public @interface QuarkusMain {
54
/**
55
* Unique name for the main method (optional).
56
* @return Main method name
57
*/
58
String name() default "";
59
}
60
```
61
62
### Build Step Annotation
63
64
```java { .api }
65
@Retention(RetentionPolicy.RUNTIME)
66
@Target(ElementType.METHOD)
67
public @interface BuildStep {
68
/**
69
* Only execute this build step if all of the given suppliers return true.
70
* @return Array of boolean suppliers for conditional execution
71
*/
72
Class<? extends BooleanSupplier>[] onlyIf() default {};
73
74
/**
75
* Only execute this build step if none of the given suppliers return true.
76
* @return Array of boolean suppliers for negative conditional execution
77
*/
78
Class<? extends BooleanSupplier>[] onlyIfNot() default {};
79
}
80
```
81
82
### Build Item Framework
83
84
```java { .api }
85
// Base class for all build items
86
public abstract class BuildItem {
87
// All build items must extend this class
88
}
89
90
// Build item for single-valued items
91
public abstract class SimpleBuildItem extends BuildItem {
92
// Used for build items that have only one instance
93
}
94
95
// Build item for multi-valued items (consumed as Lists)
96
public abstract class MultiBuildItem extends BuildItem {
97
// Used for build items that can have multiple instances
98
}
99
100
// Build producer for creating build items
101
public interface BuildProducer<T extends BuildItem> {
102
void produce(T item);
103
}
104
105
// Build consumer for consuming build items
106
public interface Consumer<T> {
107
void accept(T t);
108
}
109
```
110
111
### Record Annotation
112
113
```java { .api }
114
@Retention(RetentionPolicy.RUNTIME)
115
@Target(ElementType.METHOD)
116
public @interface Record {
117
/**
118
* Execution time for the recorded method.
119
* @return Execution time (STATIC_INIT or RUNTIME_INIT)
120
*/
121
ExecutionTime value();
122
}
123
124
// Execution time enum
125
public enum ExecutionTime {
126
STATIC_INIT, // Execute at build time
127
RUNTIME_INIT // Execute at runtime initialization
128
}
129
```
130
131
## Usage Examples
132
133
### Basic Recorder Class
134
135
```java
136
import io.quarkus.runtime.annotations.Recorder;
137
import io.quarkus.runtime.annotations.StaticInit;
138
import io.quarkus.runtime.annotations.RuntimeInit;
139
140
@Recorder
141
public class MyServiceRecorder {
142
143
@StaticInit
144
public void configureStaticSettings() {
145
// This method is executed at build time
146
// Used for static configuration that doesn't change at runtime
147
System.setProperty("quarkus.my-service.static-config", "enabled");
148
149
// Register static resources
150
registerStaticResources();
151
}
152
153
@RuntimeInit
154
public MyServiceRuntimeConfig configureRuntimeSettings(MyServiceBuildConfig buildConfig) {
155
// This method is executed at runtime startup
156
// Uses build-time configuration to create runtime configuration
157
158
MyServiceRuntimeConfig runtimeConfig = new MyServiceRuntimeConfig();
159
runtimeConfig.setEndpoint(buildConfig.endpoint);
160
runtimeConfig.setTimeout(buildConfig.timeout);
161
162
// Initialize runtime components
163
initializeRuntimeComponents(runtimeConfig);
164
165
return runtimeConfig;
166
}
167
168
private void registerStaticResources() {
169
// Register resources that are known at build time
170
}
171
172
private void initializeRuntimeComponents(MyServiceRuntimeConfig config) {
173
// Initialize components that need runtime configuration
174
}
175
}
176
```
177
178
### Configuration Classes
179
180
```java
181
import io.quarkus.runtime.annotations.ConfigItem;
182
import io.quarkus.runtime.annotations.ConfigRoot;
183
import io.quarkus.runtime.annotations.ConfigPhase;
184
185
// Build-time configuration
186
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
187
public class MyServiceBuildConfig {
188
189
/**
190
* Service endpoint URL.
191
*/
192
@ConfigItem(defaultValue = "http://localhost:8080")
193
public String endpoint;
194
195
/**
196
* Connection timeout in seconds.
197
*/
198
@ConfigItem(defaultValue = "30")
199
public int timeout;
200
201
/**
202
* Enable debug mode.
203
*/
204
@ConfigItem(defaultValue = "false")
205
public boolean debug;
206
}
207
208
// Runtime configuration
209
@ConfigRoot(phase = ConfigPhase.RUNTIME_INIT)
210
public class MyServiceRuntimeConfig {
211
212
/**
213
* Runtime service endpoint.
214
*/
215
@ConfigItem
216
public String endpoint;
217
218
/**
219
* Runtime timeout.
220
*/
221
@ConfigItem
222
public int timeout;
223
224
// Setters for recorder
225
public void setEndpoint(String endpoint) {
226
this.endpoint = endpoint;
227
}
228
229
public void setTimeout(int timeout) {
230
this.timeout = timeout;
231
}
232
}
233
```
234
235
### Database Connection Recorder
236
237
```java
238
import io.quarkus.runtime.annotations.Recorder;
239
import io.quarkus.runtime.annotations.StaticInit;
240
import io.quarkus.runtime.annotations.RuntimeInit;
241
import javax.sql.DataSource;
242
243
@Recorder
244
public class DatabaseRecorder {
245
246
@StaticInit
247
public void registerDriver(String driverClassName) {
248
// Register JDBC driver at build time
249
try {
250
Class.forName(driverClassName);
251
System.out.println("Registered JDBC driver: " + driverClassName);
252
} catch (ClassNotFoundException e) {
253
throw new RuntimeException("Failed to register JDBC driver: " + driverClassName, e);
254
}
255
}
256
257
@RuntimeInit
258
public DataSource createDataSource(DatabaseConfig config) {
259
// Create DataSource at runtime with actual connection parameters
260
HikariConfig hikariConfig = new HikariConfig();
261
hikariConfig.setJdbcUrl(config.url);
262
hikariConfig.setUsername(config.username);
263
hikariConfig.setPassword(config.password);
264
hikariConfig.setMaximumPoolSize(config.maxPoolSize);
265
hikariConfig.setConnectionTimeout(config.connectionTimeout.toMillis());
266
267
DataSource dataSource = new HikariDataSource(hikariConfig);
268
269
// Register as CDI bean
270
Arc.container().instance(DataSource.class).get().setDataSource(dataSource);
271
272
return dataSource;
273
}
274
275
@RuntimeInit
276
public void validateConnection(DataSource dataSource) {
277
// Validate database connection at startup
278
try (Connection conn = dataSource.getConnection()) {
279
if (conn.isValid(5)) {
280
System.out.println("Database connection validated successfully");
281
} else {
282
throw new RuntimeException("Database connection validation failed");
283
}
284
} catch (SQLException e) {
285
throw new RuntimeException("Failed to validate database connection", e);
286
}
287
}
288
}
289
```
290
291
### HTTP Client Recorder
292
293
```java
294
import io.quarkus.runtime.annotations.Recorder;
295
import io.quarkus.runtime.annotations.StaticInit;
296
import io.quarkus.runtime.annotations.RuntimeInit;
297
import java.net.http.HttpClient;
298
import java.time.Duration;
299
300
@Recorder
301
public class HttpClientRecorder {
302
303
@StaticInit
304
public void configureHttpClientDefaults() {
305
// Set system properties for HTTP client at build time
306
System.setProperty("jdk.httpclient.allowRestrictedHeaders", "true");
307
System.setProperty("jdk.httpclient.keepalive.timeout", "30");
308
}
309
310
@RuntimeInit
311
public HttpClient createHttpClient(HttpClientConfig config) {
312
// Create configured HTTP client at runtime
313
HttpClient.Builder builder = HttpClient.newBuilder()
314
.connectTimeout(Duration.ofSeconds(config.connectTimeout))
315
.followRedirects(HttpClient.Redirect.NORMAL);
316
317
if (config.enableHttp2) {
318
builder.version(HttpClient.Version.HTTP_2);
319
}
320
321
if (config.enableCompression) {
322
// Enable compression if supported
323
}
324
325
HttpClient client = builder.build();
326
327
// Register as singleton
328
registerHttpClientSingleton(client);
329
330
return client;
331
}
332
333
private void registerHttpClientSingleton(HttpClient client) {
334
// Register HTTP client as CDI singleton
335
}
336
}
337
```
338
339
### Security Recorder
340
341
```java
342
import io.quarkus.runtime.annotations.Recorder;
343
import io.quarkus.runtime.annotations.StaticInit;
344
import io.quarkus.runtime.annotations.RuntimeInit;
345
import java.security.Security;
346
347
@Recorder
348
public class SecurityRecorder {
349
350
@StaticInit
351
public void registerSecurityProviders() {
352
// Register security providers at build time
353
Security.addProvider(new BouncyCastleProvider());
354
System.out.println("Registered BouncyCastle security provider");
355
}
356
357
@StaticInit
358
public void configureSecurityProperties() {
359
// Configure security properties at build time
360
Security.setProperty("crypto.policy", "unlimited");
361
Security.setProperty("securerandom.source", "file:/dev/urandom");
362
}
363
364
@RuntimeInit
365
public SecurityContext initializeSecurityContext(SecurityConfig config) {
366
// Initialize security context at runtime
367
SecurityContext context = new SecurityContext();
368
369
if (config.enableJwt) {
370
JwtProcessor jwtProcessor = createJwtProcessor(config);
371
context.setJwtProcessor(jwtProcessor);
372
}
373
374
if (config.enableOAuth) {
375
OAuthProvider oauthProvider = createOAuthProvider(config);
376
context.setOAuthProvider(oauthProvider);
377
}
378
379
return context;
380
}
381
382
private JwtProcessor createJwtProcessor(SecurityConfig config) {
383
// Create JWT processor with runtime configuration
384
return new JwtProcessor(config.jwtSecret, config.jwtExpiration);
385
}
386
387
private OAuthProvider createOAuthProvider(SecurityConfig config) {
388
// Create OAuth provider with runtime configuration
389
return new OAuthProvider(config.oauthClientId, config.oauthClientSecret);
390
}
391
}
392
```
393
394
### Recordable Constructor Usage
395
396
```java
397
import io.quarkus.runtime.annotations.RecordableConstructor;
398
399
public class RecordableService {
400
private final String configuration;
401
private final int port;
402
403
// This constructor can be recorded and called during build-time processing
404
@RecordableConstructor
405
public RecordableService(String configuration, int port) {
406
this.configuration = configuration;
407
this.port = port;
408
}
409
410
// Regular constructor (not recordable)
411
public RecordableService() {
412
this("default", 8080);
413
}
414
415
public void start() {
416
System.out.println("Starting service with config: " + configuration + " on port: " + port);
417
}
418
}
419
420
// Recorder using the recordable constructor
421
@Recorder
422
public class ServiceRecorder {
423
424
@RuntimeInit
425
public RecordableService createService(ServiceConfig config) {
426
// This will generate bytecode that calls the recordable constructor
427
return new RecordableService(config.name, config.port);
428
}
429
}
430
```
431
432
### Multiple Main Methods
433
434
```java
435
import io.quarkus.runtime.annotations.QuarkusMain;
436
import io.quarkus.runtime.QuarkusApplication;
437
438
@QuarkusMain(name = "web-server")
439
public class WebServerMain implements QuarkusApplication {
440
@Override
441
public int run(String... args) throws Exception {
442
System.out.println("Starting web server");
443
// Web server logic
444
return 0;
445
}
446
}
447
448
@QuarkusMain(name = "batch-job")
449
public class BatchJobMain implements QuarkusApplication {
450
@Override
451
public int run(String... args) throws Exception {
452
System.out.println("Running batch job");
453
// Batch processing logic
454
return 0;
455
}
456
}
457
458
@QuarkusMain(name = "data-migration")
459
public class DataMigrationMain implements QuarkusApplication {
460
@Override
461
public int run(String... args) throws Exception {
462
System.out.println("Running data migration");
463
// Migration logic
464
return 0;
465
}
466
}
467
```
468
469
Usage:
470
```bash
471
# Run web server (default)
472
java -jar myapp.jar
473
474
# Run specific main method
475
java -jar myapp.jar -Dquarkus.main.name=batch-job
476
java -jar myapp.jar -Dquarkus.main.name=data-migration
477
```
478
479
### Static Init Safe Classes
480
481
```java
482
import io.quarkus.runtime.annotations.StaticInitSafe;
483
484
@StaticInitSafe
485
public class ConfigurationUtils {
486
// This class is safe to initialize during static init phase
487
488
public static final String DEFAULT_CONFIG = "default-config.properties";
489
public static final int MAX_RETRIES = 3;
490
491
static {
492
// Static initialization block is safe to run at build time
493
System.out.println("ConfigurationUtils initialized");
494
}
495
496
public static String loadConfiguration(String filename) {
497
// This method can be safely called during static initialization
498
try (InputStream is = ConfigurationUtils.class.getResourceAsStream(filename)) {
499
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
500
} catch (IOException e) {
501
throw new RuntimeException("Failed to load configuration", e);
502
}
503
}
504
}
505
506
// Recorder using static init safe class
507
@Recorder
508
public class ConfigRecorder {
509
510
@StaticInit
511
public void loadStaticConfiguration() {
512
// Safe to call static methods from @StaticInitSafe classes
513
String config = ConfigurationUtils.loadConfiguration(ConfigurationUtils.DEFAULT_CONFIG);
514
System.setProperty("app.static.config", config);
515
}
516
}
517
```
518
519
### Build Step Integration
520
521
```java
522
import io.quarkus.deployment.annotations.BuildStep;
523
import io.quarkus.deployment.annotations.Record;
524
import io.quarkus.deployment.annotations.ExecutionTime;
525
import io.quarkus.deployment.builditem.FeatureBuildItem;
526
527
public class MyServiceProcessor {
528
529
private static final String FEATURE = "my-service";
530
531
@BuildStep
532
FeatureBuildItem feature() {
533
return new FeatureBuildItem(FEATURE);
534
}
535
536
@BuildStep
537
@Record(ExecutionTime.STATIC_INIT)
538
void configureStaticInit(MyServiceRecorder recorder, MyServiceBuildConfig config) {
539
// Call recorder method during static initialization
540
recorder.configureStaticSettings();
541
542
if (config.debug) {
543
recorder.enableDebugMode();
544
}
545
}
546
547
@BuildStep
548
@Record(ExecutionTime.RUNTIME_INIT)
549
MyServiceRuntimeConfigBuildItem configureRuntimeInit(
550
MyServiceRecorder recorder,
551
MyServiceBuildConfig buildConfig) {
552
// Call recorder method during runtime initialization
553
MyServiceRuntimeConfig runtimeConfig = recorder.configureRuntimeSettings(buildConfig);
554
555
return new MyServiceRuntimeConfigBuildItem(runtimeConfig);
556
}
557
}
558
```
559
560
## Best Practices
561
562
### Recorder Design
563
564
1. **Separate concerns** between static and runtime initialization
565
2. **Use `@StaticInit`** for configuration that doesn't change at runtime
566
3. **Use `@RuntimeInit`** for components that need runtime configuration
567
4. **Mark classes as `@StaticInitSafe`** when they can be safely initialized at build time
568
5. **Use `@RecordableConstructor`** for objects created in recorders
569
570
### Performance Optimization
571
572
1. **Minimize work in `@RuntimeInit`** methods to improve startup time
573
2. **Pre-compute values** at build time when possible
574
3. **Use static initialization** for immutable configuration
575
4. **Cache expensive operations** results at build time
576
577
### Error Handling
578
579
1. **Validate configuration** early in the build process
580
2. **Provide clear error messages** for configuration issues
581
3. **Handle missing dependencies** gracefully
582
4. **Use appropriate exception types** for different failure scenarios
583
584
### Testing
585
586
1. **Test both build-time and runtime behavior**
587
2. **Verify configuration validation** works correctly
588
3. **Test with different configuration profiles**
589
4. **Ensure native image compatibility** for all recorded operations