{"record":{"id":"9ac827f0962d9ca0","repo":"oracle/graal","slug":"can-t-serialize-continuation-with-static-frames-fr","errorCode":null,"errorMessage":"Can't serialize continuation with static frames from methods of hidden classes: %s.%s","messagePattern":"Can't serialize continuation with static frames from methods of hidden classes: (.+?)\\.(.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"espresso/src/org.graalvm.continuations/src/org/graalvm/continuations/FrameRecordSerializer.java","lineNumber":167,"sourceCode":"        } catch (NoSuchMethodException e) {\n            throw new IOException(e);\n        }\n    }\n\n    private void writeMethodHolder(Method method, Object receiver) throws IOException {\n        assert out != null;\n        if (receiver != null && method.getDeclaringClass() == receiver.getClass()) {\n            /*\n             * Some classes such as Lambda classes can't be looked up by name. This is a JVM\n             * optimization designed to avoid contention on the global dictionary lock, but it means\n             * we need another way to get the class for the method. Fortunately, lambdas always have\n             * an instance, so we can read it out of the first pointer slot.\n             */\n            out.writeBoolean(true);\n        } else {\n            out.writeBoolean(false);\n            if (method.getDeclaringClass().isHidden()) {\n                throw new IOException(\"Can't serialize continuation with static frames from methods of hidden classes: %s.%s\".formatted(method.getDeclaringClass().getName(), method.getName()));\n            }\n            writeString(method.getDeclaringClass().getName());\n        }\n    }\n\n    private void writeFrame(ContinuationImpl.FrameRecord cursor) throws IOException {\n        assert out != null;\n        Method method = cursor.method;\n        writeMethodHolder(method, cursor.pointers.length > THIS_POS ? cursor.pointers[THIS_POS] : null);\n        writeString(method.getName());\n        out.writeObject(cursor.pointers);\n        out.writeObject(cursor.primitives);\n        writeMethodTypes(method);\n        out.writeInt(cursor.bci);\n    }\n\n    private void writeMethodTypes(Method method) throws IOException {\n        assert out != null;","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/espresso/src/org.graalvm.continuations/src/org/graalvm/continuations/FrameRecordSerializer.java#L149-L185","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nstatic CompletableFuture<Object> f = supplyAsync(MyHidden::work); // static frame in hidden class\n...\nContinuation.suspend(); // inside work() -> writeMethodHolder throws on serialize\n\n// after\nclass WorkTask implements Supplier<Object> { // named, instance method: receiver recorded\n    public Object get() { Continuation.suspend(); return result; }\n}\nstatic CompletableFuture<Object> f = supplyAsync(new WorkTask());","handlingStrategy":"validation","validationCode":"// before suspending, check the current stack for static frames of hidden classes\nStackWalker.getInstance().forEach(f -> {\n    Class<?> dc = f.getDeclaringClass();\n    if (dc.isHidden() && (f.isStaticMethod() || f.getDeclaringClass().getName().contains(\"$$Lambda\"))) {\n        throw new IllegalStateException(\"Cannot suspend in hidden-class frame: \" + f);\n    }\n});","typeGuard":null,"tryCatchPattern":"try { bytes = Continuation.serialize(cont); } catch (IOException e) { /* message names the hidden class.method: move that code to a named class */ }","preventionTips":["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."],"tags":["serialization","continuations","hidden-classes","lambdas","lambda"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}