{"record":{"id":"57de58ef133c7ff9","repo":"quarkusio/quarkus","slug":"unknown-recorder-constructor-parameter-s-in-reco","errorCode":null,"errorMessage":"Unknown recorder constructor parameter: %s in recorder %s","messagePattern":"Unknown recorder constructor parameter: (.+?) in recorder (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java","lineNumber":575,"sourceCode":"                        }\n                        methodParamFns.add((bc, bri) -> {\n                            assert bri != null;\n                            return bri.getRecordingProxy(parameterClass);\n                        });\n                        //now look for recorder parameter injection\n                        //as we now inject config directly into recorders we need to look at the constructor params\n                        Constructor<?>[] ctors = parameter.getType().getDeclaredConstructors();\n                        for (var ctor : ctors) {\n                            if (ctors.length == 1 || ctor.isAnnotationPresent(Inject.class)) {\n                                for (var type : ctor.getGenericParameterTypes()) {\n                                    Class<?> theType;\n                                    boolean isRuntimeValue = false;\n                                    if (type instanceof ParameterizedType pt) {\n                                        if (pt.getRawType().equals(RuntimeValue.class)) {\n                                            theType = (Class<?>) pt.getActualTypeArguments()[0];\n                                            isRuntimeValue = true;\n                                        } else {\n                                            throw new RuntimeException(\"Unknown recorder constructor parameter: \" + type\n                                                    + \" in recorder \" + parameter.getType());\n                                        }\n                                    } else {\n                                        theType = (Class<?>) type;\n                                    }\n                                    ConfigRoot annotation = theType.getAnnotation(ConfigRoot.class);\n                                    if (annotation != null) {\n                                        methodConsumingConfigPhases.add(recordAnnotation.value() == ExecutionTime.STATIC_INIT\n                                                ? ConfigPhase.BUILD_AND_RUN_TIME_FIXED\n                                                : annotation.phase());\n                                        if (annotation.phase().isAvailableAtBuild() && !annotation.phase().isAvailableAtRun()) {\n                                            throw reportError(parameter, annotation.phase() + \" configuration \"\n                                                    + type.getTypeName()\n                                                    + \" cannot be consumed in a Recorder\");\n                                        } else if (annotation.phase().isReadAtMain() && !isRuntimeValue) {\n                                            throw reportError(parameter, annotation.phase() + \" configuration \"\n                                                    + type.getTypeName()\n                                                    + \" can only be injected in a @Recorder constructor as a RuntimeValue<\"","sourceCodeStart":557,"sourceCodeEnd":593,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java#L557-L593","documentation":"Recorder constructor parameters that are generic (ParameterizedType) must be RuntimeValue<T>; any other parameterized type is unsupported. Quarkus unwraps RuntimeValue's type argument and otherwise cannot map the generic type to a runtime-injectable value, so it throws during loadStepsFromClass.","triggerScenarios":"Declaring a recorder constructor parameter like List<String>, Optional<Foo>, Map<K,V> or any other parameterized type that is not RuntimeValue<T>; the raw type check pt.getRawType().equals(RuntimeValue.class) fails.","commonSituations":"Injecting collections or config-annotated generics into a Recorder constructor expecting Quarkus to resolve them; after upgrading Quarkus which tightened the allowed parameter types.","solutions":["Change the parameter type to RuntimeValue<T> if a generic wrapped value is intended.","Use a non-generic class type for the constructor parameter (e.g. a custom config class annotated @ConfigRoot, or a plain class).","Pass the generic data at record time via a recorder method call instead of the constructor."],"exampleFix":"// before\npublic MyRecorder(List<String> names) { ... }\n// after\npublic MyRecorder(RuntimeValue<List<String>> names) { ... }\n// or: use a concrete type\npublic MyRecorder(String[] names) { ... }","handlingStrategy":"validation","validationCode":"for (Type t : ctor.getGenericParameterTypes()) {\n    if (t instanceof ParameterizedType pt && !pt.getRawType().equals(RuntimeValue.class))\n        throw new IllegalArgumentException(\"Unsupported generic recorder param: \" + t);\n}","typeGuard":"static boolean isAllowedRecorderParam(Type t) {\n    if (t instanceof ParameterizedType pt) {\n        return pt.getRawType().equals(RuntimeValue.class);\n    }\n    return t instanceof Class<?>;\n}","tryCatchPattern":null,"preventionTips":["Only use RuntimeValue<T> for generic recorder constructor parameters.","Pass generic data via recorder method calls, not constructor generics.","Keep recorder constructors limited to concrete classes or @ConfigRoot types."],"tags":["quarkus","extensions","recorder","runtime-value"],"backgroundTag":"invalid-recorder-constructor-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"}