quarkusio/quarkus · error · RuntimeException
%s is marked @Record but does not inject an @Recorder object
Error message
%s is marked @Record but does not inject an @Recorder object
What it means
Quarkus build steps marked with @Record must inject at least one @Recorder object, because @Record marks a step as bytecode-recorded (its logic runs at RUNTIME_INIT via generated recorders). During extension loading, Quarkus scans the step's parameters for a Recorder type; if none is found the extension definition is invalid and loading fails fast.
Source
Thrown at core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java:453
if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
method.setAccessible(true);
}
final Class<? extends BooleanSupplier>[] onlyIf = buildStep.onlyIf();
final Class<? extends BooleanSupplier>[] onlyIfNot = buildStep.onlyIfNot();
final Parameter[] methodParameters = method.getParameters();
final Record recordAnnotation = method.getAnnotation(Record.class);
final boolean isRecorder = recordAnnotation != null;
final boolean identityComparison = !isRecorder || recordAnnotation.useIdentityComparisonForParameters();
if (isRecorder) {
boolean recorderFound = false;
for (Class<?> p : method.getParameterTypes()) {
if (isRecorder(p)) {
recorderFound = true;
break;
}
}
if (!recorderFound) {
throw new RuntimeException(method + " is marked @Record but does not inject an @Recorder object");
}
}
final List<BiFunction<BuildContext, BytecodeRecorderImpl, Object>> methodParamFns;
Consumer<BuildStepBuilder> methodStepConfig = Functions.discardingConsumer();
BooleanSupplier addStep = () -> true;
addStep = and(addStep, supplierFactory, classOnlyIf, false);
addStep = and(addStep, supplierFactory, classOnlyIfNot, true);
addStep = and(addStep, supplierFactory, onlyIf, false);
addStep = and(addStep, supplierFactory, onlyIfNot, true);
final BooleanSupplier finalAddStep = addStep;
if (isRecorder) {
final ExecutionTime executionTime = recordAnnotation.value();
final boolean optional = recordAnnotation.optional();
methodStepConfig = methodStepConfig.andThen(bsb -> {
bsb
.produces(
executionTime == ExecutionTime.STATIC_INIT ? StaticBytecodeRecorderBuildItem.classView on GitHub (pinned to e1c734241f)
Solutions
- Add a parameter of a class annotated with @Recorder to the recorded build step and use it to record runtime logic.
- Remove the @Record annotation if the step does not actually need to record bytecode for runtime initialization.
- Split the step: keep pure build-time logic in an unrecorded step and move recorder usage into a separate @Record step.
Example fix
// before
@BuildStep
@Record(RUNTIME_INIT)
void recordSomething(MyBuildItem item) { ... }
// after
@BuildStep
@Record(RUNTIME_INIT)
void recordSomething(MyBuildItem item, MyRecorder recorder) {
recorder.doAtRuntime(item.getValue());
} Defensive patterns
Strategy: validation
Validate before calling
boolean hasRecorder = Arrays.stream(buildStepMethod.getParameterTypes())
.anyMatch(t -> t.isAnnotationPresent(Recorder.class) || Recorder.class.isAssignableFrom(t));
if (!hasRecorder) throw new IllegalStateException("@Record step " + buildStepMethod + " lacks an @Recorder parameter"); Type guard
static boolean isRecordedStepWithRecorder(Method m) {
return m.isAnnotationPresent(Record.class)
&& Arrays.stream(m.getParameterTypes()).anyMatch(t -> t.isAnnotationPresent(Recorder.class));
} Prevention
- Always pair @Record with an @Recorder parameter in the same step.
- Review extension build steps for @Record after refactors.
- Let code review check that recorded steps actually invoke a recorder method.
When it happens
Trigger: An extension author annotates a @BuildStep method with @Record(RUNTIME_INIT) (or EXECUTION) but all its parameters are non-recorder types (build items, config, etc.), so isRecorder(p) is false for every parameter during loadStepsFromClass.
Common situations: Refactoring a recorded step and removing the Recorder parameter; copy-pasting a @Record annotation onto a step that only consumes build items; misunderstanding that @Record applies to build-time bytecode generation only.
Related errors
- Unknown recorder constructor parameter: %s in recorder %s
- Cannot write service provider file '${resourceName}': this G
- One of either visitorFunction or inputTransformer must be se
- Failed to record call to method ${call.method}
- All parameters have already been loaded, it is too late to c
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/a3115778f1dba85c.
Report an issue: GitHub.