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.

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.

Appendix: 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)