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
- 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
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
- Never write after onCompleted()/halfClose()
- Track cancellation state and gate writes with a flag
- On server cancellation (onError), stop all pending sends
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
- Writes cannot be done after calling halfClose or cancel
- halfClose cannot be called after already half closed or canc
- Metric with name ${name} already exists
- ScheduledExecutorService not set in Builder
- ChannelLogger is not set in Builder
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/403ae6b7049d731a.
Report an issue: GitHub.