{"id":"d5b39c79feb79419","repo":"apache/kafka","slug":"the-stream-is-already-closed","errorCode":null,"errorMessage":"The stream is already closed","messagePattern":"The stream is already closed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/common/compress/Lz4BlockOutputStream.java","lineNumber":233,"sourceCode":"        bufferOffset += len;\n    }\n\n    @Override\n    public void flush() throws IOException {\n        if (!finished) {\n            writeBlock();\n        }\n        if (out != null) {\n            out.flush();\n        }\n    }\n\n    /**\n     * A simple state check to ensure the stream is still open.\n     */\n    private void ensureNotFinished() {\n        if (finished) {\n            throw new IllegalStateException(CLOSED_STREAM);\n        }\n    }\n\n    @Override\n    public void close() throws IOException {\n        try {\n            if (!finished) {\n                // basically flush the buffer writing the last block\n                writeBlock();\n                // write the end block\n                writeEndMark();\n            }\n        } finally {\n            try {\n                if (out != null) {\n                    try (OutputStream outStream = out) {\n                        outStream.flush();\n                    }","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/compress/Lz4BlockOutputStream.java#L215-L251","documentation":"Thrown as IllegalStateException by ensureNotFinished() in Lz4BlockOutputStream whenever write(int) or write(byte[],int,int) is invoked after the stream has been closed (the finished flag was set to true in close()). The LZ4 frame has already had its EndMark written; any further write would produce an invalid frame, so the class rejects it.","triggerScenarios":"Calling write()/write(int)/write(byte[],int,int) on a Lz4BlockOutputStream after close() has run (close() sets finished=true at line 257). Reached via ensureNotFinished() at lines 189 and 199.","commonSituations":"Try-with-resources where a callback or async path writes after the block exited; pooled/reused OutputStream wrappers that don't track lifecycle; cleanup code that closes the stream defensively and then a finally/interceptor writes a trailer; integration code that wraps a Kafka producer's compression stream and outlives the produce call; bugs in flush-then-close ordering in custom serializers.","solutions":["Audit call sites to guarantee no write() occurs after close(); reorder so close() is the last operation on the stream.","If a wrapper may legitimately receive late writes, guard with a boolean closed flag and drop or buffer them rather than passing through.","Avoid reusing the Lz4BlockOutputStream instance across produce calls — create a fresh one per batch.","Reproduce with assertions enabled and a stack trace to find the stray post-close write."],"exampleFix":"// before: cleanup closes the stream, then a callback writes to it\ntry (Lz4BlockOutputStream out = new Lz4BlockOutputStream(ch, level, broken)) {\n    out.write(payload);\n}\nlistener.onAck(() -> out.write(trailer)); // IllegalStateException\n\n// after: complete all writes before close, or guard late writes\ntry (Lz4BlockOutputStream out = new Lz4BlockOutputStream(ch, level, broken)) {\n    out.write(payload);\n    out.write(trailer);\n}","handlingStrategy":"validation","validationCode":"// Lz4BlockOutputStream flips to 'finished' after close(); writes then throw.\n// Track lifecycle externally and check before every write.\nLz4BlockOutputStream out = ...;\nif (!isOpen(out)) {                       // see typeGuard\n    throw new IllegalStateException(\"Attempted write to already-closed LZ4 stream\");\n}\nout.write(b, off, len);","typeGuard":"// There is no public isOpen() on Lz4BlockOutputStream; mirror state in a holder.\nclass Lz4Sink {\n    private final Lz4BlockOutputStream out;\n    private boolean open = true;\n    boolean isOpen() { return open; }\n    void close() throws IOException { try { out.close(); } finally { open = false; } }\n}","tryCatchPattern":"try {\n    out.write(b, off, len);\n} catch (IllegalStateException e) {\n    if (Lz4BlockOutputStream.CLOSED_STREAM.equals(e.getMessage())) {\n        // discard the write target and re-create a fresh LZ4 frame if needed\n        reopenSink();\n    } else throw e;\n}","preventionTips":["Use try-with-resources around Lz4BlockOutputStream so close() is invoked exactly once and the lifecycle is lexical.","Never share a single Lz4BlockOutputStream across threads or producer callbacks; the class is documented non-thread-safe and 'finished' is global state.","Set out = null immediately after close() at every ownership boundary and null-check before write.","If wrapping in an appender/pipeline, expose an explicit isOpen() in your wrapper rather than relying on the underlying class."],"tags":["compression","lz4","io","lifecycle","api-misuse","kafka-clients"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}