apache/iceberg · error · UncheckedIOException
UncheckedIOException wrapping IOException from bitmap…
Error message
UncheckedIOException wrapping IOException from bitmap deserialization
What it means
RoaringPositionBitmap.readBitmap deserializes a RoaringBitmap from a ByteBuffer. bitmap.deserialize throws IOException if the buffer is not a valid bitmap encoding; the method wraps it in UncheckedIOException so it can propagate from non-IO signature contexts. This indicates corrupt or malformed serialized bitmap data.
Solutions
- Verify the blob bytes and offset are the exact serialized RoaringBitmap (not truncated or shifted); regenerate the deletion vector from source delete files if corrupt
- Check writer/consumer library version compatibility for the bitmap encoding
- Catch UncheckedIOException at the read site and treat the deletion vector as unreadable rather than crashing the scan
Example fix
// before
RoaringBitmap bmp = RoaringPositionBitmap.bitmap(buffer);
// after
try { RoaringBitmap bmp = RoaringPositionBitmap.bitmap(buffer); }
catch (UncheckedIOException e) { throw new RuntimeException("Corrupt deletion vector bitmap", e); } Defensive patterns
Strategy: try-catch
Validate before calling
// check blob integrity before deserializing
if (bytes == null || bytes.remaining() < 8) throw new IllegalStateException("Truncated bitmap blob"); Try / catch
try { bitmap = RoaringPositionBitmap.bitmap(buffer); } catch (UncheckedIOException e) { throw new IOException("Corrupt deletion vector blob", e.getCause()); } Prevention
- Validate blob length/offset before deserializing
- Round-trip serialize/deserialize in tests after any writer change
- Treat UncheckedIOException here as data corruption, and re-generate DVs rather than retrying the same bytes
When it happens
Trigger: Deserializing a position bitmap from bytes that are not a valid RoaringBitmap encoding — truncated buffer, wrong offset, corrupted blob (e.g. a damaged Puffin/deletion-vector blob), or bytes produced by an incompatible version.
Common situations: Reading deletion-vector blobs written by another writer/version, hand-edited or truncated files, or passing a buffer positioned at the wrong offset into the serialization.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- Cannot serialize type: + typeId
- Failed to encode value as UTF-8
- does not support serialize
- malformed input around byte
- malformed input: partial character at end
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/b1c9932ae96e6066.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/apache/iceberg/deletes/RoaringPositionBitmap.java:317
return (int) bitmapCount;
}
private static int readKey(ByteBuffer buffer, int lastKey) {
int key = buffer.getInt();
Preconditions.checkArgument(key >= 0, "Invalid unsigned key: %s", key);
Preconditions.checkArgument(key <= Integer.MAX_VALUE - 1, "Key is too large: %s", key);
Preconditions.checkArgument(key > lastKey, "Keys must be sorted in ascending order");
return key;
}
private static RoaringBitmap readBitmap(ByteBuffer buffer) {
try {
RoaringBitmap bitmap = new RoaringBitmap();
bitmap.deserialize(buffer);
buffer.position(buffer.position() + bitmap.serializedSizeInBytes());
return bitmap;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
// extracts high 32 bits from a 64-bit position (i.e. key)
private static int key(long pos) {
return (int) (pos >> 32);
}
// extracts low 32 bits from a 64-bit position (i.e. 32-bit position)
private static int pos32Bits(long pos) {
return (int) pos;
}
// combines high and low 32 bits into a 64-bit position
// the low 32 bits must be bit-masked to avoid sign extension
private static long toPosition(int key, int pos32Bits) {
return (((long) key) << 32) | (((long) pos32Bits) & 0xFFFFFFFFL);
}View on GitHub (pinned to 86d9c8fc54)