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
- Track completion with a boolean/AtomicBoolean and only end once
- Use response.headWritten()/written state or the Future returned by end() to detect double-completion
- 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
- Return immediately after end()
- Use an AtomicBoolean to make end idempotent
- Ensure only one code path completes the response
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
- Request has already been read
- Head already written
- Response head already sent
- Response already ended
- Response has already been written
AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06).
Data as JSON: /api/errors/e51309f8b2128607.
Report an issue: GitHub.