grpc/grpc-java · error · IllegalStateException

Framer already closed

Error message

Framer already closed

What it means

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.

Source

Thrown at core/src/main/java/io/grpc/internal/MessageFramer.java:366

  }

  private void releaseBuffer() {
    if (buffer != null) {
      buffer.release();
      buffer = null;
    }
  }

  private void commitToSink(boolean endOfStream, boolean flush) {
    WritableBuffer buf = buffer;
    buffer = null;
    sink.deliverFrame(buf, endOfStream, flush, messagesBuffered);
    messagesBuffered = 0;
  }

  private void verifyNotClosed() {
    if (isClosed()) {
      throw new IllegalStateException("Framer already closed");
    }
  }

  /** OutputStream whose write()s are passed to the framer. */
  private class OutputStreamAdapter extends OutputStream {
    /**
     * This is slow, don't call it.  If you care about write overhead, use a BufferedOutputStream.
     * Better yet, you can use your own single byte buffer and call
     * {@link #write(byte[], int, int)}.
     */
    @Override
    public void write(int b) {
      byte[] singleByte = new byte[]{(byte)b};
      write(singleByte, 0, 1);
    }

    @Override
    public void write(byte[] b, int off, int len) {

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Do not write after halfClose()/onCompleted(); restructure the client callback state machine to mark the stream as done
  2. Catch IllegalStateException from write/sendMessage and treat it as stream-already-finished
  3. Check cancellation/stream completion before calling writePayload
  4. Ensure retry or replay logic creates a fresh stream instead of reusing closed framer state

Example fix

// before
responseObserver.onCompleted();
requestObserver.onNext(data); // throws IllegalStateException
// after
requestObserver.onNext(data);
requestObserver.onCompleted();
Defensive patterns

Strategy: try-catch

Validate before calling

if (callDone.get()) return; // check your own stream-finished flag before writePayload

Try / catch

try { requestObserver.onNext(msg); } catch (IllegalStateException e) { if (e.getMessage().contains("Framer already closed")) { /* stream already completed; drop or resubscribe */ } else throw e; }

Prevention

When it happens

Trigger: 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).

Common situations: Race between cancellation and sending (write after onError/onCompleted), observer callbacks invoked after stream done, retry/replay code reusing a closed framer.

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


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/403ae6b7049d731a. Report an issue: GitHub.