quarkusio/quarkus · error · RuntimeException
Unable to determine the recordable constructor to use for ${
Error message
Unable to determine the recordable constructor to use for ${param.getClass()} What it means
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.
Source
Thrown at core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java:1248
for (int i = 0; i < params.size(); i++) {
Object obj = params.get(i);
nonDefaultConstructorHandles[i] = loadObjectInstance(obj, existing,
parameterTypes[count++], relaxedValidation);
}
extractConstructorParameterNames(nonDefaultConstructorHolder.constructor, constructorParamNameMap);
} else if (classesToUseRecordableConstructor.contains(param.getClass())) {
Constructor<?> current = null;
int count = 0;
for (var c : param.getClass().getConstructors()) {
if (current == null || current.getParameterCount() < c.getParameterCount()) {
current = c;
count = 0;
} else if (current.getParameterCount() == c.getParameterCount()) {
count++;
}
}
if (current == null || count > 0) {
throw new RuntimeException("Unable to determine the recordable constructor to use for " + param.getClass());
}
nonDefaultConstructorHolder = new NonDefaultConstructorHolder(current, null);
nonDefaultConstructorHandles = new DeferredParameter[current.getParameterCount()];
extractConstructorParameterNames(current, constructorParamNameMap);
} else {
Constructor<?>[] ctors = param.getClass().getConstructors();
Constructor<?> selectedCtor = null;
if (ctors.length == 1) {
// if there is a single constructor we use it regardless of the presence of @RecordableConstructor annotation
selectedCtor = ctors[0];
}
for (Constructor<?> ctor : ctors) {
if (RecordingAnnotationsUtil.isRecordableConstructor(ctor)) {
selectedCtor = ctor;
break;
}
}View on GitHub (pinned to e1c734241f)
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
Example fix
// before
public Point(int x, int y) {...}
public Point(int x) {...}
// after
@Inject
public Point(int x, int y) {...}
public Point(int x) {...} Defensive patterns
Strategy: validation
Validate before calling
Constructor<?>[] ctors = clazz.getConstructors();
long max = Arrays.stream(ctors).mapToInt(Constructor::getParameterCount).max().orElse(-1);
long matches = Arrays.stream(ctors).filter(c -> c.getParameterCount() == max).count();
if (matches != 1 && Arrays.stream(ctors).noneMatch(c -> c.isAnnotationPresent(Inject.class))) {
throw new IllegalStateException(clazz + " has no unambiguous recordable constructor");
} Type guard
boolean hasUnambiguousCtor(Class<?> c) {
Constructor<?>[] ctors = c.getConstructors();
if (Arrays.stream(ctors).anyMatch(k -> k.isAnnotationPresent(Inject.class))) return true;
int max = Arrays.stream(ctors).mapToInt(Constructor::getParameterCount).max().orElse(-1);
return Arrays.stream(ctors).filter(k -> k.getParameterCount() == max).count() == 1;
} Try / catch
try {
recorder.record(value);
} catch (RuntimeException e) {
if (String.valueOf(e.getMessage()).startsWith("Unable to determine the recordable constructor")) {
throw new IllegalStateException("Add @Inject to one constructor of " + value.getClass(), e);
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Unable to serialize ${param} as the wrong number of paramete
- Could not find parameters for constructor ${nonDefaultConstr
- Unable to serialize objects of type ${param.getClass()} to b
- Couldn't extract all parameters information for constructor
- Cannot serialise field '${i.getName()}' on object '${param}'
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/8870dab645046c8b.
Report an issue: GitHub.