quarkusio/quarkus · error · RuntimeException
Cannot use ${method} as a recorder method as the return type
Error message
Cannot use ${method} as a recorder method as the return type cannot be proxied. Use RuntimeValue to wrap the return value instead. What it means
Recorder methods' return values must be either void, primitively recordable, or proxyable types that Quarkus can substitute with recorded proxies. If a recorder method returns a non-proxiable type (e.g. final class, primitive-wrapper misuse, unrecordable class), invoke() cannot represent the result, so it fails with guidance to wrap the value in RuntimeValue instead.
Source
Thrown at core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java:353
throw new RuntimeException("Invalid proxy passed to recorder. Parameter " + i + " of type "
+ method.getParameterTypes()[i]
+ " was created in a runtime recorder method, while this recorder is for a static init method. The object will not have been created at the time this method is run.");
}
}
}
}
StoredMethodCall storedMethodCall = new StoredMethodCall(theClass, method, args);
storedMethodCalls.add(storedMethodCall);
Class<?> returnType = method.getReturnType();
if (method.getName().equals("toString")
&& method.getParameterCount() == 0
&& returnType.equals(String.class)) {
return proxy.getClass().getName();
}
boolean voidMethod = method.getReturnType().equals(void.class);
if (!voidMethod && !isProxiable(method.getReturnType())) {
throw new RuntimeException("Cannot use " + method
+ " as a recorder method as the return type cannot be proxied. Use RuntimeValue to wrap the return value instead.");
}
if (voidMethod) {
return null;
}
ProxyInstance instance = getProxyInstance(returnType);
if (instance == null) {
return null;
}
storedMethodCall.returnedProxy = instance.proxy;
storedMethodCall.proxyId = instance.key;
return instance.proxy;
}
};
try {View on GitHub (pinned to e1c734241f)
Solutions
- Change the recorder method return type to RuntimeValue<T> and construct with new RuntimeValue<>(instance)
- Return an interface or non-final class that Quarkus can proxy
- Return a primitive/String/List/Map directly if the data is recordable
- Split the method: return recordable data, and do non-recordable work inside the recorder method itself
Example fix
// before
@Recorder
public MyFinalService service() { return new MyFinalService(); }
// after
@Recorder
public RuntimeValue<MyFinalService> service() { return new RuntimeValue<>(new MyFinalService()); } Defensive patterns
Strategy: type-guard
Validate before calling
static boolean recorderReturnOk(Class<?> t) {
return t.isPrimitive() || t == void.class || t == String.class || !Modifier.isFinal(t.getModifiers());
} Type guard
static boolean recorderReturnOk(Class<?> t) {
return t.isPrimitive() || t == void.class || t == String.class || !Modifier.isFinal(t.getModifiers());
} Prevention
- Default to RuntimeValue<T> for complex recorder return types
- Avoid final classes and records as direct recorder return values
- Add a build-time test invoking each recorder method signature
When it happens
Trigger: Declaring a @Recorder method whose return type fails isProxiable(): final classes, classes without usable constructors, or other non-recordable types returned directly instead of RuntimeValue<T>.
Common situations: Returning configuration POJOs/records/final builder classes from recorders; returning Optional or streams; returning third-party final classes from recorder APIs.
Related errors
- Public method ${methodInfo} cannot be proxied as it is final
- Unknown value of annotation member ${name}
- Method ${method} not implemented
- Invalid proxy passed to recorder. Parameter ${i} of type ${m
- Failed to record call to method ${call.method}
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/cf6d5899bc16f82d.
Report an issue: GitHub.