{"record":{"id":"2d6f59a4e6c8224f","repo":"apache/flink","slug":"illegal-attempt-to-write-to-closed-output-stream","errorCode":null,"errorMessage":"Illegal attempt to write to closed output stream","messagePattern":"Illegal attempt to write to closed output stream","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"flink-filesystems/flink-gs-fs-hadoop/src/main/java/org/apache/flink/fs/gs/writer/GSRecoverableFsDataOutputStream.java","lineNumber":146,"sourceCode":"        write(bytes);\n    }\n\n    @Override\n    public void write(@Nonnull byte[] content) throws IOException {\n        Preconditions.checkNotNull(content);\n\n        write(content, 0, content.length);\n    }\n\n    @Override\n    public void write(@Nonnull byte[] content, int start, int length) throws IOException {\n        Preconditions.checkNotNull(content);\n        Preconditions.checkArgument(start >= 0);\n        Preconditions.checkArgument(length >= 0);\n\n        // if the data stream is already closed, throw an exception\n        if (closed) {\n            throw new IOException(\"Illegal attempt to write to closed output stream\");\n        }\n\n        // if necessary, create a write channel\n        if (currentWriteChannel == null) {\n            LOGGER.debug(\"Creating write channel for blob {}\", finalBlobIdentifier);\n            currentWriteChannel = createWriteChannel();\n        }\n\n        // write to the stream. the docs say that, in some circumstances, though an attempt will be\n        // made to write all of the requested bytes, there are some cases where only some bytes will\n        // be written. it's not clear whether this could ever happen with a Google storage\n        // WriteChannel; in any case, recoverable writers don't support partial writes, so if this\n        // ever happens, we must fail the write.:\n        // https://docs.oracle.com/javase/7/docs/api/java/nio/channels/WritableByteChannel.html#write(java.nio.ByteBuffer)\n        LOGGER.trace(\"Writing {} bytes\", length);\n        int bytesWritten = currentWriteChannel.write(content, start, length);\n        if (bytesWritten != length) {\n            throw new IOException(","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/apache/flink/blob/2f3c205e9266cb30240eb7f4fdab15cad629a70f/flink-filesystems/flink-gs-fs-hadoop/src/main/java/org/apache/flink/fs/gs/writer/GSRecoverableFsDataOutputStream.java#L128-L164","documentation":"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.","triggerScenarios":"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.","commonSituations":"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().","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"],"exampleFix":"// before\nstream.write(data, 0, data.length); // may run after close() in finally\n// after\nif (!streamClosed) {\n    stream.write(data, 0, data.length);\n}","handlingStrategy":"validation","validationCode":"// track lifecycle yourself; the stream exposes no isOpen() check\nif (!streamClosed) {\n    stream.write(content, 0, content.length);\n}","typeGuard":null,"tryCatchPattern":"try {\n    stream.write(content, 0, content.length);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"closed output stream\")) {\n        // bug in caller lifecycle: open a new stream via writer.open(), do not retry this one\n    }\n}","preventionTips":["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"],"tags":["gcs","output-stream","lifecycle","io"],"backgroundTag":null,"analyzedSha":"2f3c205e9266cb30240eb7f4fdab15cad629a70f","analyzedAt":"2026-08-14T08:48:24.518Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}