eclipse-vertx/vert.x · error · IllegalStateException

Response has already been written

Error message

Response has already been written

What it means

write_() is the internal path behind response.write() and end(). Once the response has ended (ended == true), any further write or end attempt throws IllegalStateException because the response is committed and the underlying stream is finished — HTTP does not allow writing after the response completed.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/http/impl/HttpServerResponseImpl.java:398

        future = stream.writeHeaders(trailedMap, true);
      }
      Handler<Void> bodyEndHandler = this.bodyEndHandler;
      Handler<Void> endHandler = this.endHandler;
      if (bodyEndHandler != null) {
        bodyEndHandler.handle(null);
      }
      if (endHandler != null) {
        endHandler.handle(null);
      }
    }
    return future;
  }

  private Future<Void> write_(Buffer chunk, boolean end) {
    boolean sendHeaders;
    synchronized (conn) {
      if (ended) {
        throw new IllegalStateException("Response has already been written");
      }
      ended = end;
      if (end && !headWritten && requiresContentLengthHeader()) {
        headers().set(HttpHeaderNames.CONTENT_LENGTH, chunk == null ? "0" : HttpUtils.positiveLongToString(chunk.length()));
      }
      sendHeaders = prepareHeaders();
    }
    if (sendHeaders) {
      return stream.writeHead(new HttpResponseHead(status.code(), status.reasonPhrase(), headersMap), chunk, end);
    } else {
      return stream.writeChunk(chunk, end);
    }
  }

  private boolean requiresContentLengthHeader() {
    return requestMethod != HttpMethod.HEAD && status != HttpResponseStatus.NOT_MODIFIED && !headersMap.contains(HttpHeaderNames.CONTENT_LENGTH);
  }

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Ensure exactly one write/end path executes — guard with a boolean or use response.ended() if available
  2. In error handlers, check whether the response is already ended before attempting to write an error response
  3. Centralize response completion in a single helper that is idempotent
  4. Structure async flows with flatMap/transform so only one completion path runs

Example fix

// before
response.end("ok");
onError(err -> response.end("error")); // IllegalStateException if success path ran

// after
AtomicBoolean done = new AtomicBoolean();
void respond(String body) {
  if (done.compareAndSet(false, true)) response.end(body);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!response.ended()) {
  response.end(body);
}

Try / catch

try {
  response.end(chunk);
} catch (IllegalStateException e) {
  // response already written; skip
}

Prevention

When it happens

Trigger: Calling write() or end() after end() already succeeded; multiple end() calls; writing from a callback that fires after another code path ended the response.

Common situations: Error-handling code calling end() after a success path already wrote the response; async callbacks racing to write; retry logic re-invoking end() on failure; double dispatch in routing.

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