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
- Regenerate the continuation payload from a known-good serialize() call on the same runtime and retry.
- Add integrity protection (checksum/HMAC) around persisted continuation bytes so corruption is detected before deserialization.
- Confirm producer and consumer run the identical org.graalvm.continuations version (the kind byte is inside the version-gated frame body).
- 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
- Add CRC/checksum validation around persisted continuation bytes.
- Use atomic write-then-rename for payload files so partial writes never reach the reader.
- Never re-serialize a payload that failed mid-read; regenerate from a live continuation.
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
- Illegal serialized continuation is in running state.
- Missing this in serialized frame.
- Erroneous deserialization of SuspendCapability.\nread object
- You can't resume a continuation while it is being serialized
- You cannot serialize a continuation whilst it's running, as
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/74643bad52a590ac.
Report an issue: GitHub.