{"record":{"id":"cd5f5ef8b8f27701","repo":"eclipse-vertx/vert.x","slug":"response-already-ended","errorCode":null,"errorMessage":"Response already ended","messagePattern":"Response already ended","errorType":"validation","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"vertx-core/src/main/java/io/vertx/core/http/impl/HttpClientResponseImpl.java","lineNumber":219,"sourceCode":"\n  @Override\n  public List<String> cookies() {\n    synchronized (conn) {\n      if (cookies == null) {\n        cookies = new ArrayList<>();\n        cookies.addAll(headers().getAll(HttpHeaders.SET_COOKIE));\n        if (trailers != null) {\n          cookies.addAll(trailers.getAll(HttpHeaders.SET_COOKIE));\n        }\n      }\n      return cookies;\n    }\n  }\n\n  /** Must be called within a {@code synchronized (conn)} block. */\n  private void checkEnded() {\n    if (ended != null) {\n      throw new IllegalStateException(\"Response already ended\");\n    }\n  }\n\n  @Override\n  public HttpClientResponse handler(Handler<Buffer> handler) {\n    synchronized (conn) {\n      if (handler != null) {\n        checkEnded();\n      }\n      HttpEventHandler eventHandler = eventHandler(handler != null);\n      if (eventHandler != null) {\n        eventHandler.chunkHandler(handler);\n      }\n      return this;\n    }\n  }\n\n  @Override","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/eclipse-vertx/vert.x/blob/fb308bd8c3f12c79f4ae89bef67fadf6c80d036e/vertx-core/src/main/java/io/vertx/core/http/impl/HttpClientResponseImpl.java#L201-L237","documentation":"HttpClientResponseImpl.checkEnded() throws this IllegalStateException when a stream handler (handler, endHandler, exceptionHandler, customFrameHandler, streamPriorityHandler) is registered after the response has already ended. Handlers cannot be attached to a completed stream.","triggerScenarios":"Calling response.handler(...)/endHandler(...)/exceptionHandler(...) after the response body has been fully consumed or the response ended asynchronously, e.g. inside a callback that runs after body() completed.","commonSituations":"Registering handlers in a later async step (after await/end of body); retry logic re-subscribing to the same ended response object; wrapping libraries that attach handlers lazily.","solutions":["Register all handlers immediately when the response arrives, before consuming the body","Use the Future-based body()/end() API instead of registering handlers after completion","Check whether the response already ended before attaching handlers (or wrap in a try-catch if unavoidable)"],"exampleFix":"// before\nresponse.body().onSuccess(b -> { /* ... */ });\nresponse.endHandler(v -> cleanup()); // throws: already ended\n// after\nresponse.endHandler(v -> cleanup());\nresponse.body().onSuccess(b -> { /* ... */ });","handlingStrategy":"try-catch","validationCode":"// attach handlers synchronously when the response is received, before any await on body()\nresponse.endHandler(v -> cleanup());\nresponse.exceptionHandler(err -> log(err));\nresponse.handler(buffer -> accumulate(buffer));","typeGuard":null,"tryCatchPattern":"try {\n  response.streamPriorityHandler(p -> ...);\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"Response already ended\")) {\n    // response finished; nothing to subscribe to\n  } else { throw e; }\n}","preventionTips":["Register all response handlers immediately upon receipt","Prefer Future-based body()/end() consumption over late handler registration","Do not re-subscribe to a response object after it completed"],"tags":["http-client","response","lifecycle","handler"],"backgroundTag":"invalid-state-transition","analyzedSha":"fb308bd8c3f12c79f4ae89bef67fadf6c80d036e","analyzedAt":"2026-09-06T11:37:12.241Z","contentChangedAt":"2026-09-06T11:37:12.241Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}