{"record":{"id":"8870dab645046c8b","repo":"quarkusio/quarkus","slug":"unable-to-determine-the-recordable-constructor-to","errorCode":null,"errorMessage":"Unable to determine the recordable constructor to use for ${param.getClass()}","messagePattern":"Unable to determine the recordable constructor to use for (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java","lineNumber":1248,"sourceCode":"            for (int i = 0; i < params.size(); i++) {\n                Object obj = params.get(i);\n                nonDefaultConstructorHandles[i] = loadObjectInstance(obj, existing,\n                        parameterTypes[count++], relaxedValidation);\n            }\n            extractConstructorParameterNames(nonDefaultConstructorHolder.constructor, constructorParamNameMap);\n        } else if (classesToUseRecordableConstructor.contains(param.getClass())) {\n            Constructor<?> current = null;\n            int count = 0;\n            for (var c : param.getClass().getConstructors()) {\n                if (current == null || current.getParameterCount() < c.getParameterCount()) {\n                    current = c;\n                    count = 0;\n                } else if (current.getParameterCount() == c.getParameterCount()) {\n                    count++;\n                }\n            }\n            if (current == null || count > 0) {\n                throw new RuntimeException(\"Unable to determine the recordable constructor to use for \" + param.getClass());\n            }\n\n            nonDefaultConstructorHolder = new NonDefaultConstructorHolder(current, null);\n            nonDefaultConstructorHandles = new DeferredParameter[current.getParameterCount()];\n            extractConstructorParameterNames(current, constructorParamNameMap);\n        } else {\n            Constructor<?>[] ctors = param.getClass().getConstructors();\n            Constructor<?> selectedCtor = null;\n            if (ctors.length == 1) {\n                // if there is a single constructor we use it regardless of the presence of @RecordableConstructor annotation\n                selectedCtor = ctors[0];\n            }\n            for (Constructor<?> ctor : ctors) {\n                if (RecordingAnnotationsUtil.isRecordableConstructor(ctor)) {\n                    selectedCtor = ctor;\n                    break;\n                }\n            }","sourceCodeStart":1230,"sourceCodeEnd":1266,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java#L1230-L1266","documentation":"When serializing an object whose class has no default constructor and no explicitly registered recordable constructor, BytecodeRecorderImpl tries to infer which constructor to use. If multiple constructors share the same (largest) parameter count, or no constructor could be selected, the choice is ambiguous or impossible and recording fails. Quarkus requires an unambiguous way to reconstruct the object at runtime.","triggerScenarios":"Passing an object of a class with several constructors of equal parameter count (and no @Inject annotation and no registered RecordableConstructor) into a @Record method; passing an object with zero accessible constructors it can disambiguate.","commonSituations":"Value/config-holder classes in extensions with overloaded constructors; adding an extra convenience constructor to a class that previously had one, breaking the unique-parameter-count heuristic after an upgrade.","solutions":["Annotate the intended constructor with jakarta.inject.Inject so the recorder picks it explicitly","Ensure the class has a single public constructor (or one unique by parameter count)","Add a default (no-arg) constructor if the state can be set via setters/getters"],"exampleFix":"// before\npublic Point(int x, int y) {...}\npublic Point(int x) {...}\n// after\n@Inject\npublic Point(int x, int y) {...}\npublic Point(int x) {...}","handlingStrategy":"validation","validationCode":"Constructor<?>[] ctors = clazz.getConstructors();\nlong max = Arrays.stream(ctors).mapToInt(Constructor::getParameterCount).max().orElse(-1);\nlong matches = Arrays.stream(ctors).filter(c -> c.getParameterCount() == max).count();\nif (matches != 1 && Arrays.stream(ctors).noneMatch(c -> c.isAnnotationPresent(Inject.class))) {\n    throw new IllegalStateException(clazz + \" has no unambiguous recordable constructor\");\n}","typeGuard":"boolean hasUnambiguousCtor(Class<?> c) {\n    Constructor<?>[] ctors = c.getConstructors();\n    if (Arrays.stream(ctors).anyMatch(k -> k.isAnnotationPresent(Inject.class))) return true;\n    int max = Arrays.stream(ctors).mapToInt(Constructor::getParameterCount).max().orElse(-1);\n    return Arrays.stream(ctors).filter(k -> k.getParameterCount() == max).count() == 1;\n}","tryCatchPattern":"try {\n    recorder.record(value);\n} catch (RuntimeException e) {\n    if (String.valueOf(e.getMessage()).startsWith(\"Unable to determine the recordable constructor\")) {\n        throw new IllegalStateException(\"Add @Inject to one constructor of \" + value.getClass(), e);\n    }\n    throw e;\n}","preventionTips":["Give recordable value classes a single public constructor","Use @Inject to mark the intended constructor when overloads exist","Prefer default constructor + setters for recorded beans"],"tags":["quarkus","bytecode-recording","constructor"],"backgroundTag":"ambiguous-recordable-constructor","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}