eclipse-vertx/vert.x · error · IllegalStateException

Response head already sent

Error message

Response head already sent

What it means

Once the response head (status line + headers) has been written to the wire, status code, status message, chunked mode, headers, and writeContinue can no longer be modified. HttpServerResponseImpl.checkHeadWritten() throws IllegalStateException to prevent mutating an already-transmitted response head.

Source

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

    if (handler != null) {
      handler.handle(cause);
    }
  }

  void handleClose(Void v) {
    Handler<Void> handler;
    synchronized (conn) {
      closed = true;
      handler = closeHandler;
    }
    if (handler != null) {
      handler.handle(null);
    }
  }

  private void checkHeadWritten() {
    if (headWritten) {
      throw new IllegalStateException("Response head already sent");
    }
  }

  @Override
  public HttpServerResponse exceptionHandler(Handler<Throwable> handler) {
    synchronized (conn) {
      if (handler != null) {
        checkValid();
      }
      exceptionHandler = handler;
      return this;
    }
  }

  @Override
  public int getStatusCode() {
    synchronized (conn) {
      return status.code();

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Set all status codes and headers before the first write/end call on the response
  2. Track whether the response was already started (response.headWritten() if available, or your own flag) before mutating
  3. Refactor middleware so header setting happens before delegating to the next handler that writes
  4. Wrap mutations in try/catch for IllegalStateException when the response state is uncertain

Example fix

// before
response.write("chunk");
response.putHeader("X-Custom", "v"); // IllegalStateException

// after
response.putHeader("X-Custom", "v");
response.write("chunk");
Defensive patterns

Strategy: validation

Validate before calling

if (!response.headWritten()) {
  response.setStatusCode(200);
  response.putHeader("X-Custom", "v");
}

Try / catch

try {
  response.putHeader("X-Custom", "v");
} catch (IllegalStateException e) {
  // head already sent; log instead of mutating
}

Prevention

When it happens

Trigger: Calling setStatusCode, setStatusMessage, setChunked, putHeader, or writeContinue after the head was already written — typically after a first write()/end() or an earlier writeContinue flushed the head.

Common situations: Setting headers inside a handler that runs after the response started (e.g. in a write or body callback); double-dispatch in routing where one layer already wrote the response; async code paths racing to set headers.

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