or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-lifecycle.mdbuild-time.mdconfiguration.mdindex.mdlogging.mdnative-image.mdruntime-context.md

build-time.mddocs/

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