{"record":{"id":"f1b70a1253825ed9","repo":"grpc/grpc-java","slug":"cannot-alter-onreadyhandler-after-call-started-us","errorCode":null,"errorMessage":"Cannot alter onReadyHandler after call started. Use ClientResponseObserver","messagePattern":"Cannot alter onReadyHandler after call started\\. Use ClientResponseObserver","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"stub/src/main/java/io/grpc/stub/ClientCalls.java","lineNumber":491,"sourceCode":"      call.cancel(\"Cancelled by client with StreamObserver.onError()\", t);\n      aborted = true;\n    }\n\n    @Override\n    public void onCompleted() {\n      call.halfClose();\n      completed = true;\n    }\n\n    @Override\n    public boolean isReady() {\n      return call.isReady();\n    }\n\n    @Override\n    public void setOnReadyHandler(Runnable onReadyHandler) {\n      if (frozen) {\n        throw new IllegalStateException(\n            \"Cannot alter onReadyHandler after call started. Use ClientResponseObserver\");\n      }\n      this.onReadyHandler = onReadyHandler;\n    }\n\n    @Override\n    public void disableAutoInboundFlowControl() {\n      disableAutoRequestWithInitial(1);\n    }\n\n    @Override\n    public void disableAutoRequestWithInitial(int request) {\n      if (frozen) {\n        throw new IllegalStateException(\n            \"Cannot disable auto flow control after call started. Use ClientResponseObserver\");\n      }\n      Preconditions.checkArgument(request >= 0, \"Initial requests must be non-negative\");\n      initialRequest = request;","sourceCodeStart":473,"sourceCodeEnd":509,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/stub/src/main/java/io/grpc/stub/ClientCalls.java#L473-L509","documentation":"ClientCalls' ClientCallStreamObserver implementation freezes its configuration once the call starts, and setOnReadyHandler throws IllegalStateException if called afterward. To receive on-ready callbacks set from within the response observer, gRPC requires passing a ClientResponseObserver whose beforeStart hook is invoked before the call starts.","triggerScenarios":"Calling setOnReadyHandler on the ClientCallStreamObserver after the RPC has started (e.g. after asyncServerStreamingCall/asyncBidiStreamingCall returned and invoked onNext/onReady).","commonSituations":"Trying to install flow-control handlers lazily; migrating from RequestObserver patterns where the handler is set outside the observer; misunderstanding the ClientResponseObserver.beforeStart contract.","solutions":["Implement the response observer as ClientResponseObserver and set the on-ready handler in beforeStart() before the call starts","Move all setOnReadyHandler/disableAutoInboundFlowControl calls into beforeStart","If dynamic handlers are truly needed, wrap the Runnable and swap its target instead of replacing the handler"],"exampleFix":"// before\nresponseObserver.setOnReadyHandler(() -> sendMore()); // throws after start\n// after\nnew ClientResponseObserver<Req, Resp>() {\n  public void beforeStart(ClientCallStreamObserver<Req> obs) { obs.setOnReadyHandler(() -> sendMore()); }\n  public void onNext(Resp v) {}\n  ...\n}","handlingStrategy":"type-guard","validationCode":"if (observer instanceof ClientCallStreamObserver) { boolean frozen = ((ClientCallStreamObserver<?>) observer).isReady(); /* configure via ClientResponseObserver.beforeStart instead */ }","typeGuard":"boolean isFrozen(ClientCallStreamObserver<?> o) { return o instanceof ClientCallStreamObserver<?> && o.isReady(); }","tryCatchPattern":"try { observer.setOnReadyHandler(h); } catch (IllegalStateException e) { throw new IllegalStateException(\"Set handler in ClientResponseObserver.beforeStart\", e); }","preventionTips":["Always use ClientResponseObserver when needing stream configuration","Set all onReady handlers in beforeStart","Never configure observers from outside observer callbacks"],"tags":["grpc","flow-control","illegal-state","observer"],"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-17T15:17:12.973Z"}