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

  1. Audit for concurrent access to the same Continuation object and serialize all start/suspend/dematerialize calls through one owner thread or an external lock.
  2. Reproduce on a clean, single-suite build (mx build) — mixed stale classes can break FrameRecord invariants.
  3. If it reproduces deterministically on the latest GraalVM with single-threaded usage, file a GraalVM issue with the continuation code and stack trace.
  4. 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

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


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/11428272901c2130. Report an issue: GitHub.