Core deployment-time APIs and infrastructure for building Quarkus applications with native image support and development-time features
—
The Quarkus build system is built around a producer-consumer model where build steps declare their inputs and outputs through build items. This enables dependency resolution, parallel execution, and extensibility for the deployment-time augmentation process.
// Build step annotations
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.annotations.Produce;
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.Overridable;
import io.quarkus.deployment.annotations.ProduceWeak;
import io.quarkus.deployment.annotations.Weak;
// Build items base classes
import io.quarkus.deployment.builditem.BuildItem;
import io.quarkus.deployment.builditem.SimpleBuildItem;
import io.quarkus.deployment.builditem.MultiBuildItem;
// Bytecode recording
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.deployment.recording.BytecodeRecorderImpl;Marks a method as a build step that participates in the build chain.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface BuildStep {
/**
* Conditional execution suppliers - step runs only if ALL return true
*/
Class<? extends BooleanSupplier>[] onlyIf() default {};
/**
* Negative conditional execution suppliers - step runs only if ALL return false
*/
Class<? extends BooleanSupplier>[] onlyIfNot() default {};
}Usage Examples:
// Simple build step
@BuildStep
FeatureBuildItem registerFeature() {
return new FeatureBuildItem(Feature.REST);
}
// Conditional build step
@BuildStep(onlyIf = NativeOrNativeSourcesBuild.class)
void processNativeImage(BuildProducer<NativeImageResourceBuildItem> resources) {
resources.produce(new NativeImageResourceBuildItem("META-INF/services/*"));
}
// Build step with multiple conditions
@BuildStep(
onlyIf = { DevelopmentMode.class, DevServicesEnabled.class },
onlyIfNot = { TestProfile.class }
)
void setupDevServices(BuildProducer<DevServicesBuildItem> devServices) {
// Development services setup
}Interface for producing build items during build step execution.
interface BuildProducer<T extends BuildItem> {
/**
* Produces a single build item
*/
void produce(T item);
/**
* Produces multiple build items
*/
void produce(Collection<T> items);
}Usage Examples:
@BuildStep
void registerReflection(BuildProducer<ReflectiveClassBuildItem> reflectiveClasses) {
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(MyClass.class)
.constructors()
.methods()
.fields()
.build());
}
@BuildStep
void registerMultipleCapabilities(BuildProducer<CapabilityBuildItem> capabilities) {
List<CapabilityBuildItem> items = Arrays.asList(
new CapabilityBuildItem(Capability.REST),
new CapabilityBuildItem(Capability.SECURITY)
);
capabilities.produce(items);
}Marks build steps that generate bytecode for runtime execution.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Record {
/**
* When the recorded bytecode should execute
*/
ExecutionTime value() default ExecutionTime.RUNTIME_INIT;
/**
* Whether bytecode production is optional
*/
boolean optional() default false;
/**
* Use identity comparison for recorder parameters
*/
boolean useIdentityComparisonForParameters() default false;
}
enum ExecutionTime {
/**
* Execute from static initializer (build time)
*/
STATIC_INIT,
/**
* Execute from main method (runtime initialization)
*/
RUNTIME_INIT
}Usage Examples:
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void configureStaticInit(MyRecorder recorder,
ConfigurationBuildItem config) {
recorder.setupStaticConfiguration(config.getProperties());
}
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void configureRuntime(MyRecorder recorder,
LaunchModeBuildItem launchMode,
ShutdownContextBuildItem shutdownContext) {
recorder.initialize(launchMode.getLaunchMode(), shutdownContext);
}
// Optional recording - may not execute if conditions aren't met
@BuildStep
@Record(value = ExecutionTime.RUNTIME_INIT, optional = true)
void conditionalSetup(MyRecorder recorder,
Optional<DatabaseBuildItem> database) {
if (database.isPresent()) {
recorder.configureDatabase(database.get());
}
}Control execution order and dependencies between build steps.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Consume {
/**
* Build item type that must be produced before this step runs
*/
Class<? extends BuildItem> value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Produce {
/**
* Build item type this step produces
*/
Class<? extends BuildItem> value();
}
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@interface Weak {
// Marks weak dependencies that don't require execution order
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface ProduceWeak {
/**
* Weak build item type this step produces
*/
Class<? extends BuildItem> value();
}Usage Examples:
@BuildStep
@Consume(ApplicationIndexBuildItem.class)
void processAfterIndexing(ApplicationIndexBuildItem index,
BuildProducer<GeneratedClassBuildItem> generated) {
// This runs after application indexing is complete
}
@BuildStep
@Produce(CustomBuildItem.class)
CustomBuildItem createCustomItem() {
return new CustomBuildItem();
}
@BuildStep
@ProduceWeak(OptionalFeatureBuildItem.class)
OptionalFeatureBuildItem maybeProvideFeature() {
// Weak production - others can override this
return new OptionalFeatureBuildItem();
}
@BuildStep
void consumeWeakItem(@Weak OptionalFeatureBuildItem weak) {
// Weak consumption - step runs regardless of weak item presence
}@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface BuildSteps {
/**
* Class-wide conditional execution suppliers
*/
Class<? extends BooleanSupplier>[] onlyIf() default {};
/**
* Class-wide negative conditional execution suppliers
*/
Class<? extends BooleanSupplier>[] onlyIfNot() default {};
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Overridable {
// Marks build items as overridable by other extensions
}Usage Examples:
@BuildSteps(onlyIf = DevelopmentMode.class)
public class DevModeProcessor {
@BuildStep
void setupHotReload() {
// Only runs in development mode
}
@BuildStep
void setupDevConsole() {
// Also only runs in development mode
}
}
@BuildStep
@Overridable
SecurityProviderBuildItem defaultSecurity() {
return new SecurityProviderBuildItem("default");
}/**
* Base class for all build items
*/
abstract class BuildItem {}
/**
* Build item with single instance per build
*/
abstract class SimpleBuildItem extends BuildItem {}
/**
* Build item allowing multiple instances per build
*/
abstract class MultiBuildItem extends BuildItem {}// Simple build item (single instance)
public final class DatabaseConfigBuildItem extends SimpleBuildItem {
private final String url;
private final String driver;
public DatabaseConfigBuildItem(String url, String driver) {
this.url = url;
this.driver = driver;
}
public String getUrl() { return url; }
public String getDriver() { return driver; }
}
// Multi build item (multiple instances allowed)
public final class EntityBuildItem extends MultiBuildItem {
private final String className;
private final String tableName;
public EntityBuildItem(String className, String tableName) {
this.className = className;
this.tableName = tableName;
}
public String getClassName() { return className; }
public String getTableName() { return tableName; }
}Provides context for bytecode recording operations.
class RecorderContext {
/**
* Creates a new class writer for generating classes
*/
ClassCreator getClassCreator(String className);
/**
* Registers an object loader for custom object handling
*/
void registerObjectLoader(ObjectLoader loader);
/**
* Creates a recorder proxy for the given class
*/
<T> T getRecorderProxy(Class<T> recorderClass);
/**
* Converts a class to a runtime proxy
*/
RuntimeValue<Class<?>> classProxy(String className);
/**
* Creates a runtime value wrapper
*/
<T> RuntimeValue<T> newInstance(String className);
}Custom object loading during bytecode recording.
interface ObjectLoader {
/**
* Loads an object into bytecode
*/
ResultHandle load(BytecodeCreator body, Object obj, boolean staticInit);
/**
* Checks if this loader can handle the given object
*/
boolean canHandleObject(Object obj, boolean staticInit);
}Usage Examples:
public class CustomObjectLoader implements ObjectLoader {
@Override
public boolean canHandleObject(Object obj, boolean staticInit) {
return obj instanceof MyCustomType;
}
@Override
public ResultHandle load(BytecodeCreator body, Object obj, boolean staticInit) {
MyCustomType custom = (MyCustomType) obj;
return body.newInstance(
MethodDescriptor.ofConstructor(MyCustomType.class, String.class),
body.load(custom.getValue())
);
}
}
// Register in recorder context
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void setupRecording(RecorderContext context, MyRecorder recorder) {
context.registerObjectLoader(new CustomObjectLoader());
recorder.initialize();
}public class MyExtensionProcessor {
@BuildStep
FeatureBuildItem registerFeature() {
return new FeatureBuildItem("my-extension");
}
@BuildStep
CapabilityBuildItem registerCapability() {
return new CapabilityBuildItem("com.example.my-capability");
}
@BuildStep
void processConfiguration(MyExtensionConfig config,
BuildProducer<MyConfigBuildItem> producer) {
producer.produce(new MyConfigBuildItem(config));
}
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void setupRuntime(MyRecorder recorder,
MyConfigBuildItem config,
ShutdownContextBuildItem shutdown) {
recorder.initialize(config.getProperties(), shutdown);
}
}public class ConditionalProcessor {
@BuildStep(onlyIf = DatabasePresent.class)
void setupDatabase(BuildProducer<DatabaseBuildItem> database) {
// Only runs if database dependency is present
}
@BuildStep(onlyIfNot = ProductionMode.class)
void setupDevMode(BuildProducer<DevServicesBuildItem> devServices) {
// Only runs when not in production mode
}
}
public class DatabasePresent implements BooleanSupplier {
@Override
public boolean getAsBoolean() {
try {
Class.forName("javax.sql.DataSource");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}public class BuildChainCustomizer {
public static Consumer<BuildChainBuilder> customize() {
return builder -> {
builder.addBuildStep(MyProcessor.class);
builder.addFinal(MyFinalProcessor.class);
builder.addInitial(MyInitialProcessor.class);
};
}
}
// Usage in augmentor
QuarkusAugmentor augmentor = QuarkusAugmentor.builder()
.setRoot(root)
.addBuildChainCustomizer(BuildChainCustomizer.customize())
.build();Install with Tessl CLI
npx tessl i tessl/maven-io--quarkus--quarkus-core-deployment