Writing Build Steps
Quarkus extensions perform work at build time through build steps that produce and consume build items. This is the core extension mechanism.
Build Steps
A build step is a method annotated with @BuildStep in a processor class
(by convention <Feature>Processor.java in the deployment module).
@BuildStep
SomeBuildItem produce(AnotherBuildItem input) {
// Build-time logic
return new SomeBuildItem(...);
}
Build Items
Build items extend BuildItem (or a subclass) and are the data objects passed
between build steps.
SimpleBuildItem— Exactly one instance. Use for single values.MultiBuildItem— Multiple instances. Use when collecting contributions from multiple extensions (e.g., registering beans, routes).- Build items should be
finalclasses.
Key Rules
- Build steps are ordered by input/output dependencies, NOT declaration order
- If a step consumes a
BuildItem, it runs after all producers of that item - Prefer producing a single build item per step. A step that produces
multiple different item types is a sign it should be split. Use
BuildProducer<T>when you need to produce multiple instances of the same item type or produce items conditionally - Use
Optional<T>for optional consumption of build items - Use
List<T>to consume all instances of aMultiBuildItem - Build steps run during augmentation (build time), NOT at runtime
- Avoid unnecessary I/O or computation — leverage existing build items rather than duplicating work
Config Mappings in Build Steps
@ConfigMapping/@ConfigRoot interfaces are only registered as CDI beans when
they appear as a parameter in at least one @BuildStep. If a runtime bean
injects a config mapping, you must reference that config mapping in a build
step — otherwise the injection will fail with UnsatisfiedResolutionException.
See the working-with-config skill for details.
Recorders
To execute code at runtime from a build step, use a @Recorder. The recorder
class lives in the runtime module. Its methods are called from deployment
build steps, but the actual execution happens at application startup — the
recorder generates bytecode that replays those method calls at runtime.
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void setupAtRuntime(MyRecorder recorder, SomeBuildItem item) {
recorder.initialize(item.getValue());
}
Execution Times
STATIC_INIT— Runs during static initialization (beforemain()). Safe for native image. Prefer this when possible as it allows more work to be done at build time in native mode.RUNTIME_INIT— Runs at application startup (duringmain()). Use when the code needs runtime-only resources (e.g., network, threads).
Recorder Constraints
- Recorder methods must only use types available in the runtime module
- Do not pass deployment-only types to recorder methods
- Do not use build-time-only libraries inside recorder implementations
- Recorder method parameters must be recordable (the bytecode recorder writes
code that reconstructs the object, similar to how
ObjectMapperwould serialize it — but to bytecode, not JSON). Use simple value types, collections, or types with appropriate constructors/setters. This is NOT Java serialization