{"record":{"id":"8b2a83ed115b8e44","repo":"apache/iceberg","slug":"version-byte-end-of-stream-reached","errorCode":null,"errorMessage":"Version byte - end of stream reached","messagePattern":"Version byte - end of stream reached","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/encryption/KeyMetadataDecoder.java","lineNumber":56,"sourceCode":"   * <p>The {@code readSchemaVersion} is as used the version of the expected (read) schema. Datum\n   * instances created by this class will are described by the expected schema.\n   */\n  KeyMetadataDecoder(byte readSchemaVersion) {\n    this.readSchema = StandardKeyMetadata.supportedSchemaVersions().get(readSchemaVersion);\n  }\n\n  @Override\n  public StandardKeyMetadata decode(InputStream stream, StandardKeyMetadata reuse) {\n    byte writeSchemaVersion;\n\n    try {\n      writeSchemaVersion = (byte) stream.read();\n    } catch (IOException e) {\n      throw new UncheckedIOException(\"Failed to read the version byte\", e);\n    }\n\n    if (writeSchemaVersion < 0) {\n      throw new RuntimeException(\"Version byte - end of stream reached\");\n    }\n\n    Schema writeSchema = StandardKeyMetadata.supportedAvroSchemaVersions().get(writeSchemaVersion);\n\n    if (writeSchema == null) {\n      throw new UnsupportedOperationException(\n          \"Cannot resolve schema for version: \" + writeSchemaVersion);\n    }\n\n    RawDecoder<StandardKeyMetadata> decoder = decoders.get(writeSchemaVersion);\n\n    if (decoder == null) {\n      decoder = RawDecoder.create(readSchema, GenericAvroReader::create, writeSchema);\n\n      decoders.put(writeSchemaVersion, decoder);\n    }\n\n    return decoder.decode(stream, reuse);","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/encryption/KeyMetadataDecoder.java#L38-L74","documentation":"After reading the version byte, decode checks whether stream.read() returned -1 (which becomes a negative byte). If so, the stream was empty or already exhausted, meaning the key metadata blob contains no data at all.","triggerScenarios":"Calling KeyMetadataDecoder.decode with an empty InputStream, or a ByteBuffer/stream positioned at EOF, so stream.read() returns -1 on the first byte.","commonSituations":"Passing a zero-length key metadata buffer from a freshly created/never-written encrypted file; decoding an empty byte array; stream already consumed by a prior read.","solutions":["Ensure the key metadata buffer/stream is non-empty and positioned at the start before decoding","Regenerate the encrypted file's key metadata if it was written empty","Rewind or re-wrap the buffer (duplicate() and flip) before passing it to decode"],"exampleFix":"// before\ndecoder.decode(ByteBuffer.allocate(0)); // RuntimeException\n// after\nByteBuffer km = ...; // non-empty buffer with encoded key metadata\nif (km != null && km.hasRemaining()) { decoder.decode(km); }","handlingStrategy":"validation","validationCode":"if (buffer == null || buffer.remaining() == 0) {\n  throw new IllegalArgumentException(\"Key metadata buffer is empty\");\n}","typeGuard":"boolean hasKeyMetadata(ByteBuffer b) { return b != null && b.remaining() >= 1; }","tryCatchPattern":"try { return decoder.decode(stream, null); }\ncatch (RuntimeException e) {\n  if (e.getMessage().contains(\"end of stream\")) { /* empty metadata handling */ }\n  throw e;\n}","preventionTips":["Never decode zero-length or fully consumed buffers","duplicate() and reset position before decoding ByteBuffers","Treat empty key metadata as a writer bug and investigate the producing code path"],"tags":["encryption","deserialization","empty-input"],"backgroundTag":"empty-response-body","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}