eclipse-vertx/vert.x · error · IllegalStateException

Response already ended

Error message

Response already ended

What it means

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.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/http/impl/HttpClientResponseImpl.java:219

  @Override
  public List<String> cookies() {
    synchronized (conn) {
      if (cookies == null) {
        cookies = new ArrayList<>();
        cookies.addAll(headers().getAll(HttpHeaders.SET_COOKIE));
        if (trailers != null) {
          cookies.addAll(trailers.getAll(HttpHeaders.SET_COOKIE));
        }
      }
      return cookies;
    }
  }

  /** Must be called within a {@code synchronized (conn)} block. */
  private void checkEnded() {
    if (ended != null) {
      throw new IllegalStateException("Response already ended");
    }
  }

  @Override
  public HttpClientResponse handler(Handler<Buffer> handler) {
    synchronized (conn) {
      if (handler != null) {
        checkEnded();
      }
      HttpEventHandler eventHandler = eventHandler(handler != null);
      if (eventHandler != null) {
        eventHandler.chunkHandler(handler);
      }
      return this;
    }
  }

  @Override

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Register all handlers immediately when the response arrives, before consuming the body
  2. Use the Future-based body()/end() API instead of registering handlers after completion
  3. Check whether the response already ended before attaching handlers (or wrap in a try-catch if unavoidable)

Example fix

// before
response.body().onSuccess(b -> { /* ... */ });
response.endHandler(v -> cleanup()); // throws: already ended
// after
response.endHandler(v -> cleanup());
response.body().onSuccess(b -> { /* ... */ });
Defensive patterns

Strategy: try-catch

Validate before calling

// attach handlers synchronously when the response is received, before any await on body()
response.endHandler(v -> cleanup());
response.exceptionHandler(err -> log(err));
response.handler(buffer -> accumulate(buffer));

Try / catch

try {
  response.streamPriorityHandler(p -> ...);
} catch (IllegalStateException e) {
  if (e.getMessage().contains("Response already ended")) {
    // response finished; nothing to subscribe to
  } else { throw e; }
}

Prevention

When it happens

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

Common situations: 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.

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 eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06). Data as JSON: /api/errors/cd5f5ef8b8f27701. Report an issue: GitHub.