{"record":{"id":"403ae6b7049d731a","repo":"grpc/grpc-java","slug":"framer-already-closed","errorCode":null,"errorMessage":"Framer already closed","messagePattern":"Framer already closed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/io/grpc/internal/MessageFramer.java","lineNumber":366,"sourceCode":"  }\n\n  private void releaseBuffer() {\n    if (buffer != null) {\n      buffer.release();\n      buffer = null;\n    }\n  }\n\n  private void commitToSink(boolean endOfStream, boolean flush) {\n    WritableBuffer buf = buffer;\n    buffer = null;\n    sink.deliverFrame(buf, endOfStream, flush, messagesBuffered);\n    messagesBuffered = 0;\n  }\n\n  private void verifyNotClosed() {\n    if (isClosed()) {\n      throw new IllegalStateException(\"Framer already closed\");\n    }\n  }\n\n  /** OutputStream whose write()s are passed to the framer. */\n  private class OutputStreamAdapter extends OutputStream {\n    /**\n     * This is slow, don't call it.  If you care about write overhead, use a BufferedOutputStream.\n     * Better yet, you can use your own single byte buffer and call\n     * {@link #write(byte[], int, int)}.\n     */\n    @Override\n    public void write(int b) {\n      byte[] singleByte = new byte[]{(byte)b};\n      write(singleByte, 0, 1);\n    }\n\n    @Override\n    public void write(byte[] b, int off, int len) {","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/core/src/main/java/io/grpc/internal/MessageFramer.java#L348-L384","documentation":"MessageFramer buffers and delivers outgoing message frames to the stream sink. Once the framer is closed (after half-close/close), any further writePayload attempt is an invalid state, so verifyNotClosed throws this IllegalStateException.","triggerScenarios":"Calling ClientCall.writePayload / sendMessage on a call after call.halfClose() or after the stream completed/cancelled and the framer was closed; double-completing a request stream (e.g. async client onSend + halfClose ordering bugs).","commonSituations":"Race between cancellation and sending (write after onError/onCompleted), observer callbacks invoked after stream done, retry/replay code reusing a closed framer.","solutions":["Do not write after halfClose()/onCompleted(); restructure the client callback state machine to mark the stream as done","Catch IllegalStateException from write/sendMessage and treat it as stream-already-finished","Check cancellation/stream completion before calling writePayload","Ensure retry or replay logic creates a fresh stream instead of reusing closed framer state"],"exampleFix":"// before\nresponseObserver.onCompleted();\nrequestObserver.onNext(data); // throws IllegalStateException\n// after\nrequestObserver.onNext(data);\nrequestObserver.onCompleted();","handlingStrategy":"try-catch","validationCode":"if (callDone.get()) return; // check your own stream-finished flag before writePayload","typeGuard":null,"tryCatchPattern":"try { requestObserver.onNext(msg); } catch (IllegalStateException e) { if (e.getMessage().contains(\"Framer already closed\")) { /* stream already completed; drop or resubscribe */ } else throw e; }","preventionTips":["Never write after onCompleted()/halfClose()","Track cancellation state and gate writes with a flag","On server cancellation (onError), stop all pending sends"],"tags":["grpc","streaming","illegal-state","framer"],"backgroundTag":"invalid-state-transition","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}