apache/iceberg · error · RuntimeException

Version byte - end of stream reached

Error message

Version byte - end of stream reached

What it means

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.

Source

Thrown at core/src/main/java/org/apache/iceberg/encryption/KeyMetadataDecoder.java:56

   * <p>The {@code readSchemaVersion} is as used the version of the expected (read) schema. Datum
   * instances created by this class will are described by the expected schema.
   */
  KeyMetadataDecoder(byte readSchemaVersion) {
    this.readSchema = StandardKeyMetadata.supportedSchemaVersions().get(readSchemaVersion);
  }

  @Override
  public StandardKeyMetadata decode(InputStream stream, StandardKeyMetadata reuse) {
    byte writeSchemaVersion;

    try {
      writeSchemaVersion = (byte) stream.read();
    } catch (IOException e) {
      throw new UncheckedIOException("Failed to read the version byte", e);
    }

    if (writeSchemaVersion < 0) {
      throw new RuntimeException("Version byte - end of stream reached");
    }

    Schema writeSchema = StandardKeyMetadata.supportedAvroSchemaVersions().get(writeSchemaVersion);

    if (writeSchema == null) {
      throw new UnsupportedOperationException(
          "Cannot resolve schema for version: " + writeSchemaVersion);
    }

    RawDecoder<StandardKeyMetadata> decoder = decoders.get(writeSchemaVersion);

    if (decoder == null) {
      decoder = RawDecoder.create(readSchema, GenericAvroReader::create, writeSchema);

      decoders.put(writeSchemaVersion, decoder);
    }

    return decoder.decode(stream, reuse);

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Ensure the key metadata buffer/stream is non-empty and positioned at the start before decoding
  2. Regenerate the encrypted file's key metadata if it was written empty
  3. Rewind or re-wrap the buffer (duplicate() and flip) before passing it to decode

Example fix

// before
decoder.decode(ByteBuffer.allocate(0)); // RuntimeException
// after
ByteBuffer km = ...; // non-empty buffer with encoded key metadata
if (km != null && km.hasRemaining()) { decoder.decode(km); }
Defensive patterns

Strategy: validation

Validate before calling

if (buffer == null || buffer.remaining() == 0) {
  throw new IllegalArgumentException("Key metadata buffer is empty");
}

Type guard

boolean hasKeyMetadata(ByteBuffer b) { return b != null && b.remaining() >= 1; }

Try / catch

try { return decoder.decode(stream, null); }
catch (RuntimeException e) {
  if (e.getMessage().contains("end of stream")) { /* empty metadata handling */ }
  throw e;
}

Prevention

When it happens

Trigger: Calling KeyMetadataDecoder.decode with an empty InputStream, or a ByteBuffer/stream positioned at EOF, so stream.read() returns -1 on the first byte.

Common situations: 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.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/8b2a83ed115b8e44. Report an issue: GitHub.