oracle/graal · error · FormatVersionException

Could not deserialize a continuation object.

Error message

Could not deserialize a continuation object.

What it means

Thrown as FormatVersionException by the static bridge ContinuationSerializable.readObjectExternal when the requested type is neither a subtype of Continuation nor of SuspendCapability. This dispatcher is the deserialization entry for the whole 'continuation object' family; it refuses types outside that hierarchy because no reader exists for them.

Source

Thrown at espresso/src/org.graalvm.continuations/src/org/graalvm/continuations/ContinuationSerializable.java:101

     *
     * @param type The type of the object to deserialize. Can be either {@link Continuation} or
     *            {@link SuspendCapability}.
     * @param loader The class loader that will be used to deserialize the stack record. If
     *            {@code null}, the {@link Thread#getContextClassLoader() current thread context
     *            class loader} will be used.
     * @param registerFreshObject A callback to the serialization framework. Intended to be used to
     *            register the in-construction object, so the framework may handle object graph
     *            cycles.
     */
    @SuppressWarnings("unchecked")
    public static <T extends ContinuationSerializable> T readObjectExternal(Class<T> type, ObjectInput in, ClassLoader loader, Consumer<T> registerFreshObject)
                    throws IOException, ClassNotFoundException {
        if (Continuation.class.isAssignableFrom(type)) {
            return (T) ContinuationImpl.readObjectExternal(in, loader, (Consumer<Continuation>) registerFreshObject);
        } else if (SuspendCapability.class.isAssignableFrom(type)) {
            return (T) SuspendCapability.readObjectExternal(in, (Consumer<SuspendCapability>) registerFreshObject);
        } else {
            throw new FormatVersionException("Could not deserialize a continuation object.");
        }
    }

    // region internals
    abstract void writeObjectExternal(ObjectOutput out) throws IOException;
}

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Only route classes that extend Continuation (ContinuationImpl) or SuspendCapability through this API.
  2. For your own serializable types, implement readObjectExternal/writeObjectExternal on a ContinuationSerializable subclass instead of calling the static bridge.
  3. Validate the class before calling: Continuation.class.isAssignableFrom(type) || SuspendCapability.class.isAssignableFrom(type).
  4. If the type should be a continuation but is not, check that your class extends the public Continuation API class from org.graalvm.continuations, not a look-alike from another package.

Example fix

// before
T obj = ContinuationSerializable.readObjectExternal(type, in, loader, this::register);

// after
if (!Continuation.class.isAssignableFrom(type) && !SuspendCapability.class.isAssignableFrom(type)) {
    throw new IllegalArgumentException(type + " is not deserializable by the continuation framework");
}
T obj = ContinuationSerializable.readObjectExternal(type, in, loader, this::register);
Defensive patterns

Strategy: type-guard

Type guard

static boolean isContinuationSerializableType(Class<?> t) {
    return Continuation.class.isAssignableFrom(t) || SuspendCapability.class.isAssignableFrom(t);
}

Prevention

When it happens

Trigger: Calling ContinuationSerializable.readObjectExternal(MyType.class, in, loader, reg) where MyType does not extend Continuation or SuspendCapability; passing an interface or Class<?> obtained reflectively that looks like a serializable continuation but is not.

Common situations: Generic deserialization frameworks (Kryo/Java serialization bridges, polyglot serializers) that route arbitrary classes through this entry point; copy-pasting a readObjectExternal call from continuation code into a custom serializable class.

Related errors


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