oracle/graal · error · IOException

Unexpected kind:

Error message

Unexpected kind: 

What it means

Thrown as IOException by FrameRecordSerializer.readClass when the kind byte read from the stream matches none of 'I','Z','D','F','J','B','C','S','V','L'. Those single-letter codes encode the 9 primitive/void kinds plus 'L' for reference types (followed by a pooled class name); any other value means the reader is misaligned with the writer — i.e. stream corruption or an incompatible format version slipped past the header check.

Source

Thrown at espresso/src/org.graalvm.continuations/src/org/graalvm/continuations/FrameRecordSerializer.java:335

        throw new NoSuchMethodException("%s %s.%s(%s)".formatted(
                        returnType.getName(), declaringClass.getName(), name, String.join(", ", Arrays.stream(argTypes).map(Class::getName).toList())));
    }

    private Class<?> readClass(ClassLoader classLoader) throws IOException, ClassNotFoundException {
        assert in != null;
        int kind = in.readUnsignedByte();
        return switch (kind) {
            case 'I' -> int.class;
            case 'Z' -> boolean.class;
            case 'D' -> double.class;
            case 'F' -> float.class;
            case 'J' -> long.class;
            case 'B' -> byte.class;
            case 'C' -> char.class;
            case 'S' -> short.class;
            case 'V' -> void.class;
            case 'L' -> Class.forName(readString(), false, classLoader);
            default -> throw new IOException("Unexpected kind: " + kind);
        };
    }

    private String readString() throws IOException {
        assert in != null;
        int idx = in.readChar();
        if ((idx & NEW_POOL_MASK) != 0) {
            String value = in.readUTF();
            idx = idx & POOL_IDX_MASK;
            if (idx == stringPoolRead.size()) {
                stringPoolRead.add(value);
            } else {
                stringPoolRead.set(idx, value);
            }
            return value;
        } else {
            assert idx <= MAX_POOL_IDX;
            return stringPoolRead.get(idx);

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Regenerate the continuation payload from a known-good serialize() call on the same runtime and retry.
  2. Add integrity protection (checksum/HMAC) around persisted continuation bytes so corruption is detected before deserialization.
  3. Confirm producer and consumer run the identical org.graalvm.continuations version (the kind byte is inside the version-gated frame body).
  4. Discard the payload on this IOException — the reader's cursor is desynchronized, so nothing after this point is trustworthy.

Example fix

// before
store.write(serialize(cont)); // partial write on crash -> truncated stream
...
Continuation.deserialize(read(), loader);

// after
// write-then-atomic-rename plus CRC
byte[] blob = serialize(cont);
store.atomicWrite("cont.bin", blob, crc32(blob));
if (crc32(read()) != expectedCRC) throw new CorruptPayloadException();
Continuation.deserialize(read(), loader);
Defensive patterns

Strategy: try-catch

Try / catch

try { Continuation.deserialize(bytes, loader); } catch (IOException e) { /* 'Unexpected kind: N' => stream misaligned/corrupt or version drift: discard payload */ }

Prevention

When it happens

Trigger: Deserializing a frame whose type descriptor bytes were truncated or shifted (partial write, bad offset); reading a payload whose frame layout came from a different (unrecognized) format version; a custom ObjectInput implementation returning garbage for readUnsignedByte.

Common situations: Payload corrupted in storage or transit; two ends of a continuation exchange built from different library versions; test harnesses feeding hand-built streams into deserialize.

Related errors


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