{"record":{"id":"43bed23ce37ae832","repo":"grpc/grpc-java","slug":"retriablestream-writemessage-should-not-be-calle","errorCode":null,"errorMessage":"RetriableStream.writeMessage() should not be called directly","messagePattern":"RetriableStream\\.writeMessage\\(\\) should not be called directly","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/io/grpc/internal/RetriableStream.java","lineNumber":574,"sourceCode":"    synchronized (lock) {\n      if (!state.passThrough) {\n        state.buffer.add(bufferEntry);\n      }\n      savedDrainedSubstreams = state.drainedSubstreams;\n    }\n\n    for (Substream substream : savedDrainedSubstreams) {\n      bufferEntry.runWith(substream);\n    }\n  }\n\n  /**\n   * Do not use it directly. Use {@link #sendMessage(Object)} instead because we don't use\n   * InputStream for buffering.\n   */\n  @Override\n  public final void writeMessage(InputStream message) {\n    throw new IllegalStateException(\"RetriableStream.writeMessage() should not be called directly\");\n  }\n\n  final void sendMessage(final ReqT message) {\n    State savedState = state;\n    if (savedState.passThrough) {\n      savedState.winningSubstream.stream.writeMessage(method.streamRequest(message));\n      return;\n    }\n\n    class SendMessageEntry implements BufferEntry {\n      @Override\n      public void runWith(Substream substream) {\n        substream.stream.writeMessage(method.streamRequest(message));\n        // TODO(ejona): Workaround Netty memory leak. Message writes always need to be followed by\n        // flushes (or half close), but retry appears to have a code path that the flushes may\n        // not happen. The code needs to be fixed and this removed. See #9340.\n        substream.stream.flush();\n      }","sourceCodeStart":556,"sourceCodeEnd":592,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/core/src/main/java/io/grpc/internal/RetriableStream.java#L556-L592","documentation":"RetriableStream buffers messages internally (sendMessage) rather than taking raw InputStreams, so the ClientStream.writeMessage(InputStream) entry point is deliberately blocked. It's marked final and always throws IllegalStateException to catch misuse.","triggerScenarios":"Calling retriableStream.writeMessage(InputStream) directly instead of sendMessage(Object) — typically when custom stream interception code treats the call as a plain ClientStream.","commonSituations":"Writing a custom ClientInterceptor or transport wrapper that forwards writeMessage() on the retriable stream; reflection-based code invoking the ClientStream interface method.","solutions":["Call sendMessage(message) (or ClientCall.sendMessage) with the typed message instead of writeMessage(InputStream)","Route InputStream-based writes through grpc's serialization (MethodDescriptor.marshaller) via sendMessage","If writing an interceptor, skip RetriableStream special handling and only wrap the underlying stream's writeMessage"],"exampleFix":"// before\n((ClientStream) call).writeMessage(marshaller.stream(message));\n// after\ncall.sendMessage(message); // ClientCall#sendMessage, which buffers for retry","handlingStrategy":"type-guard","validationCode":"if (stream instanceof RetriableStream) { /* use sendMessage(Object), not writeMessage(InputStream) */ }","typeGuard":"static boolean isRetriable(ClientStream s) { return s instanceof RetriableStream; }","tryCatchPattern":"try { stream.writeMessage(in); } catch (IllegalStateException e) { if (e.getMessage().contains(\"should not be called directly\")) { /* switch to sendMessage */ } else throw e; }","preventionTips":["Always send messages via ClientCall.sendMessage, never the internal ClientStream","In interceptors, forward calls instead of bypassing the call abstraction","Don't cast streams to ClientStream to write raw InputStreams"],"tags":["grpc","retriable-stream","illegal-state","api-misuse"],"backgroundTag":"unsupported-operation","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}