oracle/graal · error · IOException
Can't serialize continuation with static frames from methods
Error message
Can't serialize continuation with static frames from methods of hidden classes: %s.%s
What it means
Thrown as IOException by FrameRecordSerializer.writeMethodHolder while serializing frames: the frame's method is declared in a hidden class (method.getDeclaringClass().isHidden()) and the serializer could not fall back to the receiver. Hidden classes (lambdas, MethodHandles.Lookup.defineHiddenClass, records in some compilers) have no stable lookup-by-name, and unlike instance frames — where the lambda's `this` in slot THIS_POS lets the reader recover the class — a static frame has no receiver, so the declaring class would be unrecoverable on deserialization.
Source
Thrown at espresso/src/org.graalvm.continuations/src/org/graalvm/continuations/FrameRecordSerializer.java:167
} catch (NoSuchMethodException e) {
throw new IOException(e);
}
}
private void writeMethodHolder(Method method, Object receiver) throws IOException {
assert out != null;
if (receiver != null && method.getDeclaringClass() == receiver.getClass()) {
/*
* Some classes such as Lambda classes can't be looked up by name. This is a JVM
* optimization designed to avoid contention on the global dictionary lock, but it means
* we need another way to get the class for the method. Fortunately, lambdas always have
* an instance, so we can read it out of the first pointer slot.
*/
out.writeBoolean(true);
} else {
out.writeBoolean(false);
if (method.getDeclaringClass().isHidden()) {
throw new IOException("Can't serialize continuation with static frames from methods of hidden classes: %s.%s".formatted(method.getDeclaringClass().getName(), method.getName()));
}
writeString(method.getDeclaringClass().getName());
}
}
private void writeFrame(ContinuationImpl.FrameRecord cursor) throws IOException {
assert out != null;
Method method = cursor.method;
writeMethodHolder(method, cursor.pointers.length > THIS_POS ? cursor.pointers[THIS_POS] : null);
writeString(method.getName());
out.writeObject(cursor.pointers);
out.writeObject(cursor.primitives);
writeMethodTypes(method);
out.writeInt(cursor.bci);
}
private void writeMethodTypes(Method method) throws IOException {
assert out != null;View on GitHub (pinned to a66e9ccd1d)
Solutions
- Move the code that suspends out of the hidden/static frame into a normal named (non-hidden) class or instance method.
- Convert the static helper into an instance method so the receiver (`this`) is recorded in THIS_POS and the class is recoverable on read.
- Identify the hidden class on the stack at suspend time from the exception message (holder.name + method name) and restructure that call site.
- If a framework generates the hidden class, check for a config option to emit named classes instead (e.g. disable lambda metafactory hiding or use -Djava.lang.invoke.stringConcat / equivalent toggles for the generator).
Example fix
// before
static CompletableFuture<Object> f = supplyAsync(MyHidden::work); // static frame in hidden class
...
Continuation.suspend(); // inside work() -> writeMethodHolder throws on serialize
// after
class WorkTask implements Supplier<Object> { // named, instance method: receiver recorded
public Object get() { Continuation.suspend(); return result; }
}
static CompletableFuture<Object> f = supplyAsync(new WorkTask()); Defensive patterns
Strategy: validation
Validate before calling
// before suspending, check the current stack for static frames of hidden classes
StackWalker.getInstance().forEach(f -> {
Class<?> dc = f.getDeclaringClass();
if (dc.isHidden() && (f.isStaticMethod() || f.getDeclaringClass().getName().contains("$$Lambda"))) {
throw new IllegalStateException("Cannot suspend in hidden-class frame: " + f);
}
}); Try / catch
try { bytes = Continuation.serialize(cont); } catch (IOException e) { /* message names the hidden class.method: move that code to a named class */ } Prevention
- Keep suspend calls out of lambdas, method refs, and framework-generated hidden classes; put them in named instance methods.
- Prefer instance methods on named classes for suspendable code so `this` is recorded.
- Check the exception message for the hidden class name and restructure that exact call site.
When it happens
Trigger: Suspending (and thus serializing) inside a static method of a hidden class: a static lambda body (e.g. a static method reference invoked reflectively), MethodHandles hidden-class targets, or hidden classes generated by frameworks, where the frame has no `this` pointer.
Common situations: Framework-generated bytecode (mocking libs, AOP, ORM enhancers, JSP/GraalVM substrate-generated classes) running on the continuation stack when it suspends; method references to static helpers of hidden classes; Lombok/annotation-processor generated hidden classes on the stack at suspend time.
Related errors
- You can't resume a continuation while it is being serialized
- You cannot serialize a continuation whilst it's running, as
- This VM does not support continuations.
- Unsupported serialized continuation version: %s\nCurrent sup
- Illegal serialized continuation is in running state.
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/9ac827f0962d9ca0.
Report an issue: GitHub.