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 it

View on GitHub (pinned to e1c734241f)

Solutions

  1. Read the 'Caused by' exception to identify which parameter failed to serialize
  2. 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)
  3. Register an ObjectSubstitution for the unsupported type via @Record with RecorderBytecodeTransformer/AnnotationProxy or the substitutions mechanism
  4. If a String parameter exceeds 65535 chars, store it in a file/resource and pass the location instead
  5. 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

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


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/dad86ab3da3e0757. Report an issue: GitHub.