quarkusio/quarkus · error · RuntimeException
Failed to record call to method ${call.method}
Error message
Failed to record call to method ${call.method} What it means
During bytecode generation the recorder wraps any exception thrown while serializing a recorded method call's parameters into this RuntimeException, naming the recorded method. It is a wrapper: the real cause is the underlying serialization failure (unsupported type, substitution failure, oversized string, etc.). Inspect the 'Caused by' chain to find the actual problem.
Source
Thrown at core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java:493
ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) {
return method.newInstance(ofConstructor(call.theClass));
}
};
classInstanceVariables.put(call.theClass, value);
}
try {
//for every parameter that was passed into the method we create a deferred value
//this will allocate a space in the array, so the value can be deserialized correctly
//even if the code for an invocation is split over several methods
Class<?>[] parameterTypes = call.method.getParameterTypes();
Annotation[][] parameterAnnotations = call.method.getParameterAnnotations();
for (int i = 0; i < call.parameters.length; ++i) {
call.deferredParameters[i] = loadObjectInstance(call.parameters[i], parameterMap,
parameterTypes[i], Arrays.stream(parameterAnnotations[i])
.anyMatch(s -> s.annotationType() == RelaxedValidation.class));
}
} catch (Exception e) {
throw new RuntimeException("Failed to record call to method " + call.method, e);
}
}
}
for (var e : existingRecorderValues.entrySet().stream()
.sorted(Comparator.comparing(re -> re.getKey().getName())).toList()) {
e.getValue().preWrite(parameterMap);
}
//when this is true it is no longer possible to allocate items in the array. this is a guard against programmer error
loadComplete = true;
//now we now know many items we have, create the array
MethodDescriptor createArrayDescriptor = ofMethod(mainMethod.getMethodDescriptor().getDeclaringClass(), CREATE_ARRAY,
"[Ljava/lang/Object;");
ResultHandle array = mainMethod.invokeVirtualMethod(createArrayDescriptor, mainMethod.getThis());
//this context manages the creation of new methods
//it tracks the number of instruction groups and when they hit a threshold itView on GitHub (pinned to e1c734241f)
Solutions
- Read the 'Caused by' exception to identify which parameter failed to serialize
- Ensure recorder method parameters are limited to types BytecodeRecorderImpl supports (primitives, String, enums, collections, maps, Optional, URL, Duration, Class, arrays, RuntimeValue proxies, or registered substitutions)
- Register an ObjectSubstitution for the unsupported type via @Record with RecorderBytecodeTransformer/AnnotationProxy or the substitutions mechanism
- If a String parameter exceeds 65535 chars, store it in a file/resource and pass the location instead
- Check the parameter is not lazily throwing when serialized (e.g. a config value accessed at the wrong phase)
Example fix
// before: recorder receives an unsupported type recorder.setClient(new SomeRuntimeClient()); // after: pass serializable data (e.g. config values or primitives) recorder.setClientConfig(host, port);
Defensive patterns
Strategy: try-catch
Validate before calling
// before calling a recorder method in a build step
static boolean isRecordable(Object p) {
return p == null || p instanceof String || p instanceof Number || p instanceof Boolean
|| p instanceof Enum || p instanceof Class || p instanceof Optional
|| p instanceof java.net.URL || p instanceof java.time.Duration
|| p instanceof java.util.Collection || p instanceof java.util.Map
|| p instanceof io.quarkus.runtime.RuntimeValue;
} Type guard
static boolean isRecordableType(Class<?> t) {
return t.isPrimitive() || String.class.isAssignableFrom(t) || Number.class.isAssignableFrom(t)
|| Boolean.class.isAssignableFrom(t) || Enum.class.isAssignableFrom(t)
|| Class.class.isAssignableFrom(t) || Collection.class.isAssignableFrom(t)
|| Map.class.isAssignableFrom(t) || Optional.class.isAssignableFrom(t);
} Try / catch
try {
recorder.someMethod(param);
} catch (RuntimeException e) {
if (e.getCause() != null) {
throw new IllegalStateException("Recorder parameter not serializable: " + param.getClass(), e.getCause());
}
throw e;
} Prevention
- Pass only primitives, Strings, enums, collections, maps, Optional, Class and RuntimeValue proxies through recorders
- Register an ObjectSubstitution for any custom type that must cross the recorder boundary
- Read the full 'Caused by' chain — this error is always a wrapper
- Keep recorded payloads small; move big data to build-time-generated resources
When it happens
Trigger: Any @Record* recorder method invocation whose argument cannot be serialized by BytecodeRecorderImpl.loadObjectInstance when writeBytecode() runs at build time; e.g. passing an unsupported object type, an object whose ObjectSubstitution.serialize() throws, or a String >65535 chars into a recorder method.
Common situations: Extension developers passing runtime objects (clients, services, streams) or large generated values into recorder methods; after upgrading Quarkus a type that was previously handled loses support; custom ObjectSubstitution implementations throwing during serialize().
Related errors
- All parameters have already been loaded, it is too late to c
- Failed to substitute ${param}
- String too large to record: ${param}
- Unsupported wildcard type: ${wildcard}
- Cannot call getValue() at deployment time
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/dad86ab3da3e0757.
Report an issue: GitHub.