oracle/graal · error · IllegalContinuationStateException

You cannot serialize a continuation whilst it's running, as

Error message

You cannot serialize a continuation whilst it's running, as this would have unclear semantics. Please suspend first.

What it means

IllegalContinuationStateException from ContinuationImpl.writeObjectExternal: serializing a continuation while its state is RUNNING is forbidden because the semantics (which frame state to persist?) are undefined. The continuation must be in a suspended (or new/completed) state before writeObject-style serialization.

Source

Thrown at espresso/src/org.graalvm.continuations/src/org/graalvm/continuations/ContinuationImpl.java:602

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        /*
         * We read the context classloader here because we need the classloader that holds the
         * user's app. If we use Class.forName() in this code we get the platform classloader
         * because this class is provided by the VM, and thus can't look up methods of user classes.
         * If we use the classloader of the entrypoint it breaks for Generator and any other classes
         * we might want to ship with the VM that use this API. So we need the user's app class
         * loader. We could walk the stack to find it just like ObjectInputStream does, but we go
         * with the context classloader here to make it easier for the user to control.
         */
        state = State.INCOMPLETE;
        readObjectExternalImpl(in, Thread.currentThread().getContextClassLoader());
    }

    @Override
    synchronized void writeObjectExternal(ObjectOutput out) throws IOException {
        State currentState = lock();
        if (currentState == State.RUNNING) {
            throw new IllegalContinuationStateException("You cannot serialize a continuation whilst it's running, as this would have unclear semantics. Please suspend first.");
        }
        try {
            ensureMaterialized();
            // We start by writing out a header byte. The high nibble contains a major version. Old
            // libraries will refuse to deserialize continuations with a higher version than what
            // they recognize. New libraries may choose to continue supporting the old formats. The
            // low nibble contains flags.
            int header = FORMAT_VERSION << FORMAT_SHIFT;

            out.writeByte(header);

            out.writeObject(currentState);
            out.writeObject(entryPoint);

            if (currentState == State.SUSPENDED) {
                FrameRecordSerializer.forOut(FORMAT_VERSION, out).writeRecord(stackFrameHead);
            }
        } finally {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Suspend the continuation first (SuspendCapability.suspend) and serialize only after resume() returned.
  2. Trigger checkpoints from a suspension callback rather than an external timer thread.
  3. Check getState() == SUSPENDED before serializing.
  4. For live migration, design an explicit suspend-then-migrate handshake.

Example fix

// before
// checkpoint thread, while workload runs:
out.writeObject(continuation); // RUNNING -> exception

// after
if (continuation.getState() == Continuation.State.SUSPENDED) {
    out.writeObject(continuation);
} else {
    requestSuspend(); // workload suspends at next safe point; serialize after resume() returns
}
Defensive patterns

Strategy: validation

Validate before calling

if (continuation.getState() == Continuation.State.SUSPENDED) {
    out.writeObject(continuation);
} else {
    requestSuspendThenCheckpoint(); // suspend first, serialize after resume() returns
}

Prevention

When it happens

Trigger: Calling ObjectOutputStream.writeObject on a continuation that is currently executing (resume() not yet returned); checkpointing from a monitor thread while the workload runs; serializing inside the entry point before suspending.

Common situations: Periodic checkpointers that snapshot state on a timer regardless of suspension; trying to migrate live tasks between nodes.

Related errors


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