{"record":{"id":"a3115778f1dba85c","repo":"quarkusio/quarkus","slug":"s-is-marked-record-but-does-not-inject-an-recor","errorCode":null,"errorMessage":"%s is marked @Record but does not inject an @Recorder object","messagePattern":"(.+?) is marked @Record but does not inject an @Recorder object","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java","lineNumber":453,"sourceCode":"            if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {\n                method.setAccessible(true);\n            }\n            final Class<? extends BooleanSupplier>[] onlyIf = buildStep.onlyIf();\n            final Class<? extends BooleanSupplier>[] onlyIfNot = buildStep.onlyIfNot();\n            final Parameter[] methodParameters = method.getParameters();\n            final Record recordAnnotation = method.getAnnotation(Record.class);\n            final boolean isRecorder = recordAnnotation != null;\n            final boolean identityComparison = !isRecorder || recordAnnotation.useIdentityComparisonForParameters();\n            if (isRecorder) {\n                boolean recorderFound = false;\n                for (Class<?> p : method.getParameterTypes()) {\n                    if (isRecorder(p)) {\n                        recorderFound = true;\n                        break;\n                    }\n                }\n                if (!recorderFound) {\n                    throw new RuntimeException(method + \" is marked @Record but does not inject an @Recorder object\");\n                }\n            }\n            final List<BiFunction<BuildContext, BytecodeRecorderImpl, Object>> methodParamFns;\n            Consumer<BuildStepBuilder> methodStepConfig = Functions.discardingConsumer();\n            BooleanSupplier addStep = () -> true;\n            addStep = and(addStep, supplierFactory, classOnlyIf, false);\n            addStep = and(addStep, supplierFactory, classOnlyIfNot, true);\n            addStep = and(addStep, supplierFactory, onlyIf, false);\n            addStep = and(addStep, supplierFactory, onlyIfNot, true);\n            final BooleanSupplier finalAddStep = addStep;\n\n            if (isRecorder) {\n                final ExecutionTime executionTime = recordAnnotation.value();\n                final boolean optional = recordAnnotation.optional();\n                methodStepConfig = methodStepConfig.andThen(bsb -> {\n                    bsb\n                            .produces(\n                                    executionTime == ExecutionTime.STATIC_INIT ? StaticBytecodeRecorderBuildItem.class","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java#L435-L471","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n@BuildStep\n@Record(RUNTIME_INIT)\nvoid recordSomething(MyBuildItem item) { ... }\n// after\n@BuildStep\n@Record(RUNTIME_INIT)\nvoid recordSomething(MyBuildItem item, MyRecorder recorder) {\n    recorder.doAtRuntime(item.getValue());\n}","handlingStrategy":"validation","validationCode":"boolean hasRecorder = Arrays.stream(buildStepMethod.getParameterTypes())\n    .anyMatch(t -> t.isAnnotationPresent(Recorder.class) || Recorder.class.isAssignableFrom(t));\nif (!hasRecorder) throw new IllegalStateException(\"@Record step \" + buildStepMethod + \" lacks an @Recorder parameter\");","typeGuard":"static boolean isRecordedStepWithRecorder(Method m) {\n    return m.isAnnotationPresent(Record.class)\n        && Arrays.stream(m.getParameterTypes()).anyMatch(t -> t.isAnnotationPresent(Recorder.class));\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["quarkus","extensions","build-step","recorder"],"backgroundTag":"missing-recorder-parameter","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}