eclipse-vertx/vert.x · error · IllegalStateException

Response has already been written

Error message

Response has already been written

What it means

Thrown by the private end(Buffer, PromiseInternal) when a second end() call is attempted after the response was fully written (written == true). An HTTP/1 response body can only be terminated once; the constant RESPONSE_WRITTEN holds the message.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1ServerResponse.java:399

  }

  @Override
  public Future<Void> end(String chunk, String enc) {
    return end(Buffer.buffer(chunk, enc));
  }

  @Override
  public Future<Void> end(Buffer chunk) {
    PromiseInternal<Void> promise = context.promise();
    end(chunk, promise);
    return promise.future();
  }

  private void end(Buffer chunk, PromiseInternal<Void> listener) {
    checkThread();
    synchronized (conn) {
      if (written) {
        throw new IllegalStateException(RESPONSE_WRITTEN);
      }
      written = true;
      ByteBuf data = ((BufferInternal)chunk).getByteBuf();
      bytesWritten += data.readableBytes();
      VertxHttpObject msg;
      if (!headWritten) {
        // if the head was not written yet we can write out everything in one go
        // which is cheaper.
        prepareHeaders(bytesWritten);
        msg = new VertxFullHttpResponse(head, version, status, data, headers, trailingHeaders);
      } else {
        msg = new VertxLastHttpContent(data, trailingHeaders);
      }
      conn.write(msg, listener);
      if (bodyEndHandler != null) {
        bodyEndHandler.handle(null);
      }
      if (!closed && endHandler != null) {

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Track completion with a boolean/AtomicBoolean and only end once
  2. Use response.headWritten()/written state or the Future returned by end() to detect double-completion
  3. Structure handlers so exactly one path ends the response (early-return after end)

Example fix

// before
if (err != null) { response.end("err"); }
response.end(result);
// after
if (err != null) { response.end("err"); return; }
response.end(result);
Defensive patterns

Strategy: validation

Validate before calling

// guard with a flag or check ended state via the response future
if (!ended.get()) { ended.set(true); response.end(data); }

Try / catch

try { response.end(data); } catch (IllegalStateException e) { /* response already ended */ }

Prevention

When it happens

Trigger: Calling response.end() twice, or end(buffer) after end() already completed, e.g. from multiple code paths or both a success and a fallback handler.

Common situations: Async flows where both an error callback and success callback call end; middleware plus handler each ending the response; retry logic around end().

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/e51309f8b2128607. Report an issue: GitHub.