apache/flink · error · IOException
Illegal attempt to write to closed output stream
Error message
Illegal attempt to write to closed output stream
What it means
GSRecoverableFsDataOutputStream.write throws this IOException when bytes are written after the stream has already been closed. The GCS recoverable output stream is single-use: once close() (or closeForCommit()) has run, the underlying write channel state is final and any further write is a programming error.
Source
Thrown at flink-filesystems/flink-gs-fs-hadoop/src/main/java/org/apache/flink/fs/gs/writer/GSRecoverableFsDataOutputStream.java:146
write(bytes);
}
@Override
public void write(@Nonnull byte[] content) throws IOException {
Preconditions.checkNotNull(content);
write(content, 0, content.length);
}
@Override
public void write(@Nonnull byte[] content, int start, int length) throws IOException {
Preconditions.checkNotNull(content);
Preconditions.checkArgument(start >= 0);
Preconditions.checkArgument(length >= 0);
// if the data stream is already closed, throw an exception
if (closed) {
throw new IOException("Illegal attempt to write to closed output stream");
}
// if necessary, create a write channel
if (currentWriteChannel == null) {
LOGGER.debug("Creating write channel for blob {}", finalBlobIdentifier);
currentWriteChannel = createWriteChannel();
}
// write to the stream. the docs say that, in some circumstances, though an attempt will be
// made to write all of the requested bytes, there are some cases where only some bytes will
// be written. it's not clear whether this could ever happen with a Google storage
// WriteChannel; in any case, recoverable writers don't support partial writes, so if this
// ever happens, we must fail the write.:
// https://docs.oracle.com/javase/7/docs/api/java/nio/channels/WritableByteChannel.html#write(java.nio.ByteBuffer)
LOGGER.trace("Writing {} bytes", length);
int bytesWritten = currentWriteChannel.write(content, start, length);
if (bytesWritten != length) {
throw new IOException(View on GitHub (pinned to 2f3c205e92)
Solutions
- Audit call sites to guarantee no write occurs after close()/closeForCommit(); track stream lifecycle explicitly
- Open a fresh stream with GSRecoverableWriter.open(path) or resume via recover(writer, resumeRecoverable) instead of writing to the closed instance
- If resuming after a failure, use the GSResumeRecoverable from keep() / persist(), never the closed stream object
Example fix
// before
stream.write(data, 0, data.length); // may run after close() in finally
// after
if (!streamClosed) {
stream.write(data, 0, data.length);
} Defensive patterns
Strategy: validation
Validate before calling
// track lifecycle yourself; the stream exposes no isOpen() check
if (!streamClosed) {
stream.write(content, 0, content.length);
} Try / catch
try {
stream.write(content, 0, content.length);
} catch (IOException e) {
if (e.getMessage().contains("closed output stream")) {
// bug in caller lifecycle: open a new stream via writer.open(), do not retry this one
}
} Prevention
- Close streams exactly once, in the owner that opened them
- Never write from finally/cleanup paths
- After recovery, always reopen via GSRecoverableWriter.open()/recover(), never reuse the old stream reference
When it happens
Trigger: Calling write(byte[], int, int) (or write(byte[])) on a GSRecoverableFsDataOutputStream instance after close() or closeForCommit() was already invoked on that instance; e.g. a sink flushing buffers in a finally block after an earlier close, or two code paths closing and then reusing the same stream.
Common situations: Custom sink implementations that retry a failed write after the stream was closed; error-handling paths that close the stream and then attempt a final flush/write; reusing a stream reference across task restarts or recovery attempts instead of opening a new one via GSRecoverableWriter.open().
Related errors
- WriteChannel.write wrote %d of %d requested bytes, failing.
- Stream is closed
- Stream is already closed
- RecoverableWriter has been closed
- Error while waiting for job to be initialized
AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14).
Data as JSON: /api/errors/2d6f59a4e6c8224f.
Report an issue: GitHub.