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
- Set all status codes and headers before the first write/end call on the response
- Track whether the response was already started (response.headWritten() if available, or your own flag) before mutating
- Refactor middleware so header setting happens before delegating to the next handler that writes
- 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
- Set status and headers before the first write/end
- Avoid mutating response state in late callbacks
- Centralize header configuration before dispatching to handlers
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
- Not a streaming upload
- Request has already been read
- Response has already been written
- size must be > 0
- maxExecuteTime must be > 0
AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06).
Data as JSON: /api/errors/52bcb6093e72084f.
Report an issue: GitHub.