CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkus--quarkus-core

Quarkus core components - runtime library for the Cloud Native, Container First Java framework

Pending
Overview
Eval results
Files

build-time.mddocs/

Build-Time Processing

The Build-Time Processing capability provides the recorder system for build-time code generation and bytecode manipulation in Quarkus applications.

Recorder System

Recorder Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Recorder {
    // Marks classes as build-time recorders for bytecode generation
}

Initialization Phase Annotations

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface StaticInit {
    // Marks recorder methods for static initialization phase (build-time)
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RuntimeInit {
    // Marks recorder methods for runtime initialization phase (startup)
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface StaticInitSafe {
    // Marks classes as safe for static initialization
}

Constructor Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
public @interface RecordableConstructor {
    // Marks constructors as recordable for bytecode generation
}

Main Method Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface QuarkusMain {
    /**
     * Unique name for the main method (optional).
     * @return Main method name
     */
    String name() default "";
}

Build Step Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BuildStep {
    /**
     * Only execute this build step if all of the given suppliers return true.
     * @return Array of boolean suppliers for conditional execution
     */
    Class<? extends BooleanSupplier>[] onlyIf() default {};
    
    /**
     * Only execute this build step if none of the given suppliers return true.
     * @return Array of boolean suppliers for negative conditional execution
     */
    Class<? extends BooleanSupplier>[] onlyIfNot() default {};
}

Build Item Framework

// Base class for all build items
public abstract class BuildItem {
    // All build items must extend this class
}

// Build item for single-valued items
public abstract class SimpleBuildItem extends BuildItem {
    // Used for build items that have only one instance
}

// Build item for multi-valued items (consumed as Lists)
public abstract class MultiBuildItem extends BuildItem {
    // Used for build items that can have multiple instances
}

// Build producer for creating build items
public interface BuildProducer<T extends BuildItem> {
    void produce(T item);
}

// Build consumer for consuming build items
public interface Consumer<T> {
    void accept(T t);
}

Record Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Record {
    /**
     * Execution time for the recorded method.
     * @return Execution time (STATIC_INIT or RUNTIME_INIT)
     */
    ExecutionTime value();
}

// Execution time enum
public enum ExecutionTime {
    STATIC_INIT,    // Execute at build time
    RUNTIME_INIT    // Execute at runtime initialization
}

Usage Examples

Basic Recorder Class

import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.annotations.StaticInit;
import io.quarkus.runtime.annotations.RuntimeInit;

@Recorder
public class MyServiceRecorder {
    
    @StaticInit
    public void configureStaticSettings() {
        // This method is executed at build time
        // Used for static configuration that doesn't change at runtime
        System.setProperty("quarkus.my-service.static-config", "enabled");
        
        // Register static resources
        registerStaticResources();
    }
    
    @RuntimeInit  
    public MyServiceRuntimeConfig configureRuntimeSettings(MyServiceBuildConfig buildConfig) {
        // This method is executed at runtime startup
        // Uses build-time configuration to create runtime configuration
        
        MyServiceRuntimeConfig runtimeConfig = new MyServiceRuntimeConfig();
        runtimeConfig.setEndpoint(buildConfig.endpoint);
        runtimeConfig.setTimeout(buildConfig.timeout);
        
        // Initialize runtime components
        initializeRuntimeComponents(runtimeConfig);
        
        return runtimeConfig;
    }
    
    private void registerStaticResources() {
        // Register resources that are known at build time
    }
    
    private void initializeRuntimeComponents(MyServiceRuntimeConfig config) {
        // Initialize components that need runtime configuration
    }
}

Configuration Classes

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.quarkus.runtime.annotations.ConfigPhase;

// Build-time configuration
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
public class MyServiceBuildConfig {
    
    /**
     * Service endpoint URL.
     */
    @ConfigItem(defaultValue = "http://localhost:8080")
    public String endpoint;
    
    /**
     * Connection timeout in seconds.
     */
    @ConfigItem(defaultValue = "30")
    public int timeout;
    
    /**
     * Enable debug mode.
     */
    @ConfigItem(defaultValue = "false")
    public boolean debug;
}

// Runtime configuration
@ConfigRoot(phase = ConfigPhase.RUNTIME_INIT)
public class MyServiceRuntimeConfig {
    
    /**
     * Runtime service endpoint.
     */
    @ConfigItem
    public String endpoint;
    
    /**
     * Runtime timeout.
     */
    @ConfigItem
    public int timeout;
    
    // Setters for recorder
    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }
    
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
}

Database Connection Recorder

import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.annotations.StaticInit;
import io.quarkus.runtime.annotations.RuntimeInit;
import javax.sql.DataSource;

@Recorder
public class DatabaseRecorder {
    
    @StaticInit
    public void registerDriver(String driverClassName) {
        // Register JDBC driver at build time
        try {
            Class.forName(driverClassName);
            System.out.println("Registered JDBC driver: " + driverClassName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Failed to register JDBC driver: " + driverClassName, e);
        }
    }
    
    @RuntimeInit
    public DataSource createDataSource(DatabaseConfig config) {
        // Create DataSource at runtime with actual connection parameters
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(config.url);
        hikariConfig.setUsername(config.username);
        hikariConfig.setPassword(config.password);
        hikariConfig.setMaximumPoolSize(config.maxPoolSize);
        hikariConfig.setConnectionTimeout(config.connectionTimeout.toMillis());
        
        DataSource dataSource = new HikariDataSource(hikariConfig);
        
        // Register as CDI bean
        Arc.container().instance(DataSource.class).get().setDataSource(dataSource);
        
        return dataSource;
    }
    
    @RuntimeInit
    public void validateConnection(DataSource dataSource) {
        // Validate database connection at startup
        try (Connection conn = dataSource.getConnection()) {
            if (conn.isValid(5)) {
                System.out.println("Database connection validated successfully");
            } else {
                throw new RuntimeException("Database connection validation failed");
            }
        } catch (SQLException e) {
            throw new RuntimeException("Failed to validate database connection", e);
        }
    }
}

HTTP Client Recorder

import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.annotations.StaticInit;
import io.quarkus.runtime.annotations.RuntimeInit;
import java.net.http.HttpClient;
import java.time.Duration;

@Recorder
public class HttpClientRecorder {
    
    @StaticInit
    public void configureHttpClientDefaults() {
        // Set system properties for HTTP client at build time
        System.setProperty("jdk.httpclient.allowRestrictedHeaders", "true");
        System.setProperty("jdk.httpclient.keepalive.timeout", "30");
    }
    
    @RuntimeInit
    public HttpClient createHttpClient(HttpClientConfig config) {
        // Create configured HTTP client at runtime
        HttpClient.Builder builder = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(config.connectTimeout))
            .followRedirects(HttpClient.Redirect.NORMAL);
        
        if (config.enableHttp2) {
            builder.version(HttpClient.Version.HTTP_2);
        }
        
        if (config.enableCompression) {
            // Enable compression if supported
        }
        
        HttpClient client = builder.build();
        
        // Register as singleton
        registerHttpClientSingleton(client);
        
        return client;
    }
    
    private void registerHttpClientSingleton(HttpClient client) {
        // Register HTTP client as CDI singleton
    }
}

Security Recorder

import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.annotations.StaticInit;
import io.quarkus.runtime.annotations.RuntimeInit;
import java.security.Security;

@Recorder
public class SecurityRecorder {
    
    @StaticInit
    public void registerSecurityProviders() {
        // Register security providers at build time
        Security.addProvider(new BouncyCastleProvider());
        System.out.println("Registered BouncyCastle security provider");
    }
    
    @StaticInit
    public void configureSecurityProperties() {
        // Configure security properties at build time
        Security.setProperty("crypto.policy", "unlimited");
        Security.setProperty("securerandom.source", "file:/dev/urandom");
    }
    
    @RuntimeInit
    public SecurityContext initializeSecurityContext(SecurityConfig config) {
        // Initialize security context at runtime
        SecurityContext context = new SecurityContext();
        
        if (config.enableJwt) {
            JwtProcessor jwtProcessor = createJwtProcessor(config);
            context.setJwtProcessor(jwtProcessor);
        }
        
        if (config.enableOAuth) {
            OAuthProvider oauthProvider = createOAuthProvider(config);
            context.setOAuthProvider(oauthProvider);
        }
        
        return context;
    }
    
    private JwtProcessor createJwtProcessor(SecurityConfig config) {
        // Create JWT processor with runtime configuration
        return new JwtProcessor(config.jwtSecret, config.jwtExpiration);
    }
    
    private OAuthProvider createOAuthProvider(SecurityConfig config) {
        // Create OAuth provider with runtime configuration
        return new OAuthProvider(config.oauthClientId, config.oauthClientSecret);
    }
}

Recordable Constructor Usage

import io.quarkus.runtime.annotations.RecordableConstructor;

public class RecordableService {
    private final String configuration;
    private final int port;
    
    // This constructor can be recorded and called during build-time processing
    @RecordableConstructor
    public RecordableService(String configuration, int port) {
        this.configuration = configuration;
        this.port = port;
    }
    
    // Regular constructor (not recordable)
    public RecordableService() {
        this("default", 8080);
    }
    
    public void start() {
        System.out.println("Starting service with config: " + configuration + " on port: " + port);
    }
}

// Recorder using the recordable constructor
@Recorder
public class ServiceRecorder {
    
    @RuntimeInit
    public RecordableService createService(ServiceConfig config) {
        // This will generate bytecode that calls the recordable constructor
        return new RecordableService(config.name, config.port);
    }
}

Multiple Main Methods

import io.quarkus.runtime.annotations.QuarkusMain;
import io.quarkus.runtime.QuarkusApplication;

@QuarkusMain(name = "web-server")
public class WebServerMain implements QuarkusApplication {
    @Override
    public int run(String... args) throws Exception {
        System.out.println("Starting web server");
        // Web server logic
        return 0;
    }
}

@QuarkusMain(name = "batch-job")
public class BatchJobMain implements QuarkusApplication {
    @Override
    public int run(String... args) throws Exception {
        System.out.println("Running batch job");
        // Batch processing logic
        return 0;
    }
}

@QuarkusMain(name = "data-migration")
public class DataMigrationMain implements QuarkusApplication {
    @Override
    public int run(String... args) throws Exception {
        System.out.println("Running data migration");
        // Migration logic
        return 0;
    }
}

Usage:

# Run web server (default)
java -jar myapp.jar

# Run specific main method
java -jar myapp.jar -Dquarkus.main.name=batch-job
java -jar myapp.jar -Dquarkus.main.name=data-migration

Static Init Safe Classes

import io.quarkus.runtime.annotations.StaticInitSafe;

@StaticInitSafe
public class ConfigurationUtils {
    // This class is safe to initialize during static init phase
    
    public static final String DEFAULT_CONFIG = "default-config.properties";
    public static final int MAX_RETRIES = 3;
    
    static {
        // Static initialization block is safe to run at build time
        System.out.println("ConfigurationUtils initialized");
    }
    
    public static String loadConfiguration(String filename) {
        // This method can be safely called during static initialization
        try (InputStream is = ConfigurationUtils.class.getResourceAsStream(filename)) {
            return new String(is.readAllBytes(), StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new RuntimeException("Failed to load configuration", e);
        }
    }
}

// Recorder using static init safe class
@Recorder
public class ConfigRecorder {
    
    @StaticInit
    public void loadStaticConfiguration() {
        // Safe to call static methods from @StaticInitSafe classes
        String config = ConfigurationUtils.loadConfiguration(ConfigurationUtils.DEFAULT_CONFIG);
        System.setProperty("app.static.config", config);
    }
}

Build Step Integration

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.builditem.FeatureBuildItem;

public class MyServiceProcessor {
    
    private static final String FEATURE = "my-service";
    
    @BuildStep
    FeatureBuildItem feature() {
        return new FeatureBuildItem(FEATURE);
    }
    
    @BuildStep
    @Record(ExecutionTime.STATIC_INIT)
    void configureStaticInit(MyServiceRecorder recorder, MyServiceBuildConfig config) {
        // Call recorder method during static initialization
        recorder.configureStaticSettings();
        
        if (config.debug) {
            recorder.enableDebugMode();
        }
    }
    
    @BuildStep
    @Record(ExecutionTime.RUNTIME_INIT)
    MyServiceRuntimeConfigBuildItem configureRuntimeInit(
            MyServiceRecorder recorder, 
            MyServiceBuildConfig buildConfig) {
        // Call recorder method during runtime initialization
        MyServiceRuntimeConfig runtimeConfig = recorder.configureRuntimeSettings(buildConfig);
        
        return new MyServiceRuntimeConfigBuildItem(runtimeConfig);
    }
}

Best Practices

Recorder Design

  1. Separate concerns between static and runtime initialization
  2. Use @StaticInit for configuration that doesn't change at runtime
  3. Use @RuntimeInit for components that need runtime configuration
  4. Mark classes as @StaticInitSafe when they can be safely initialized at build time
  5. Use @RecordableConstructor for objects created in recorders

Performance Optimization

  1. Minimize work in @RuntimeInit methods to improve startup time
  2. Pre-compute values at build time when possible
  3. Use static initialization for immutable configuration
  4. Cache expensive operations results at build time

Error Handling

  1. Validate configuration early in the build process
  2. Provide clear error messages for configuration issues
  3. Handle missing dependencies gracefully
  4. Use appropriate exception types for different failure scenarios

Testing

  1. Test both build-time and runtime behavior
  2. Verify configuration validation works correctly
  3. Test with different configuration profiles
  4. Ensure native image compatibility for all recorded operations

Install with Tessl CLI

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

docs

application-lifecycle.md

build-time.md

configuration.md

index.md

logging.md

native-image.md

runtime-context.md

tile.json