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
- Suspend the continuation first (SuspendCapability.suspend) and serialize only after resume() returned.
- Trigger checkpoints from a suspension callback rather than an external timer thread.
- Check getState() == SUSPENDED before serializing.
- 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
- Checkpoint only from suspension points, not from timer threads.
- Always check getState() == SUSPENDED before serializing.
- Design migration as an explicit suspend-then-serialize handshake.
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
- You can't resume a continuation while it is being serialized
- You can't resume an already executing continuation.
- This continuation has already completed successfully.
- This continuation has failed and must be discarded.
- Only new or suspended continuation can be resumed
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/ca045966e081c84d.
Report an issue: GitHub.