oracle/graal · error · IllegalMaterializedRecordException
Failed to dematerialize continuation frames.
Error message
Failed to dematerialize continuation frames.
What it means
Thrown as IllegalMaterializedRecordException by ContinuationImpl's dematerialize path: after dematerialize0() ran (with the continuation locked), stackFrameHead is still non-null. stackFrameHead anchors the linked list of materialized FrameRecords, so a non-null head after dematerialization means frames were left behind — an internal invariant violation of the continuation bookkeeping rather than a user input error.
Source
Thrown at espresso/src/org.graalvm.continuations/src/org/graalvm/continuations/ContinuationImpl.java:791
private void ensureDematerialized() {
if (stackFrameHead == null) {
// No frame to dematerialize
return;
}
synchronized (this) {
State previousState = lock();
if (previousState == State.RUNNING) {
dematerialize0();
} else {
try {
dematerialize0();
} finally {
unlock(previousState);
}
}
}
if (stackFrameHead != null) {
throw new IllegalMaterializedRecordException("Failed to dematerialize continuation frames.");
}
}
@Override
public String toString() {
String status;
switch (getState()) {
case NEW: // fallthrough
case SUSPENDED:
status = "Resumable";
break;
case RUNNING:
status = "Running";
break;
case COMPLETED: // fallthrough
case FAILED:
status = "Completed";
break;View on GitHub (pinned to a66e9ccd1d)
Solutions
- Audit for concurrent access to the same Continuation object and serialize all start/suspend/dematerialize calls through one owner thread or an external lock.
- Reproduce on a clean, single-suite build (mx build) — mixed stale classes can break FrameRecord invariants.
- If it reproduces deterministically on the latest GraalVM with single-threaded usage, file a GraalVM issue with the continuation code and stack trace.
- Do not retry dematerialize() on the same instance — the FrameRecord chain is already in an unknown state; create a new continuation instead.
Example fix
// before
new Thread(() -> cont.start(entry)).start();
new Thread(() -> cont.dematerialize()).start(); // races on FrameRecords
// after
// single owner thread drives every lifecycle transition
executor.execute(() -> {
cont.start(entry);
cont.suspend();
cont.dematerialize();
}); Defensive patterns
Strategy: validation
Validate before calling
// before any lifecycle call, assert single ownership
if (!contOwnerThread.compareAndSet(null, Thread.currentThread())) {
throw new IllegalStateException("Continuation already owned by " + contOwnerThread.get());
} Try / catch
try { cont.dematerialize(); } catch (IllegalMaterializedRecordException e) { /* internal invariant broken: abandon this instance, report bug upstream */ } Prevention
- Drive every start/suspend/dematerialize transition of a Continuation from one owner thread.
- Never retry dematerialize on the same instance after this exception.
- Reproduce on a clean single-version build before reporting; mixed builds break frame-record invariants.
When it happens
Trigger: dematerialize() or the internal record-pathway (e.g. during suspend/serialization support) completing while FrameRecords remain reachable from stackFrameHead; typically caused by a bug in the frame-walking/record logic or by concurrent manipulation of the continuation from multiple threads.
Common situations: Two threads resuming/suspending/dematerializing the same Continuation instance concurrently; hitting a bug in a specific GraalVM build's continuation instrumentation; a partially-updated workspace mixing old and new espresso/continuation classes.
Related errors
- You can't resume an already executing continuation.
- You can't resume a continuation while it is being serialized
- Only new or suspended continuation can be resumed
- Suspend capabilities can only be used inside a continuation.
- Cannot signal lock that is not held
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/11428272901c2130.
Report an issue: GitHub.