apache/iceberg · error · IOException
Writing to closed stream
Error message
Writing to closed stream
What it means
AesGcmOutputStream refuses writes after close() has been called. Iceberg's AES-GCM stream writer finalizes the last encrypted block and writes trailers during close; any subsequent write would corrupt the ciphertext, so it throws IOException immediately.
Source
Thrown at core/src/main/java/org/apache/iceberg/encryption/AesGcmOutputStream.java:74
this.cipherBlock = new byte[Ciphers.CIPHER_BLOCK_SIZE];
this.positionInPlainBlock = 0;
this.currentBlockIndex = 0;
this.isHeaderWritten = false;
this.lastBlockWritten = false;
this.isClosed = false;
this.finalPosition = 0;
}
@Override
public void write(int b) throws IOException {
singleByte[0] = (byte) (b & 0x000000FF);
write(singleByte);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (isClosed) {
throw new IOException("Writing to closed stream");
}
if (!isHeaderWritten) {
writeHeader();
}
if (b.length - off < len) {
throw new IOException(
"Insufficient bytes in buffer: " + b.length + " - " + off + " < " + len);
}
int remaining = len;
int offset = off;
while (remaining > 0) {
int freeBlockBytes = plainBlock.length - positionInPlainBlock;
int toWrite = Math.min(freeBlockBytes, remaining);
View on GitHub (pinned to 86d9c8fc54)
Solutions
- Remove the extra write after close, or move it before close()
- Stop double-closing: rely on try-with-resources and don't call close() manually too
- If the stream may be closed, create a new AesGcmOutputStream instead of reusing the closed one
- Guard writes with an isOpen/closed flag in the owning code
Example fix
// before out.close(); out.write(footerBytes); // after out.write(footerBytes); out.close();
Defensive patterns
Strategy: try-catch
Validate before calling
if (!streamClosed) { out.write(data); } Try / catch
try {
out.write(data);
} catch (IOException e) {
if ("Writing to closed stream".equals(e.getMessage())) {
out = createNewStream(); // recreate instead of reusing
} else { throw e; }
} Prevention
- Use try-with-resources exclusively; never close manually as well
- Do not cache and reuse output stream instances across operations
- Buffer all data before closing, never write after close
When it happens
Trigger: Calling write(int), write(byte[]) or write(byte[],int,int) on an AesGcmOutputStream after close() has already been invoked.
Common situations: Double-closing a stream via try-with-resources plus explicit close(), then writing a final marker; writing to a cached stream instance that a previous code path already closed; retry logic that reuses a closed output stream after a failure.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Failed to get stream length: no open stream
- Failed to close encryption manager
- Failed to get stream length
- Failed to read header and fingerprint bytes
- File length is null
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/b62fc6801a4ab757.
Report an issue: GitHub.