{"record":{"id":"e51309f8b2128607","repo":"eclipse-vertx/vert.x","slug":"response-has-already-been-written-e51309","errorCode":null,"errorMessage":"Response has already been written","messagePattern":"Response has already been written","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1ServerResponse.java","lineNumber":399,"sourceCode":"  }\n\n  @Override\n  public Future<Void> end(String chunk, String enc) {\n    return end(Buffer.buffer(chunk, enc));\n  }\n\n  @Override\n  public Future<Void> end(Buffer chunk) {\n    PromiseInternal<Void> promise = context.promise();\n    end(chunk, promise);\n    return promise.future();\n  }\n\n  private void end(Buffer chunk, PromiseInternal<Void> listener) {\n    checkThread();\n    synchronized (conn) {\n      if (written) {\n        throw new IllegalStateException(RESPONSE_WRITTEN);\n      }\n      written = true;\n      ByteBuf data = ((BufferInternal)chunk).getByteBuf();\n      bytesWritten += data.readableBytes();\n      VertxHttpObject msg;\n      if (!headWritten) {\n        // if the head was not written yet we can write out everything in one go\n        // which is cheaper.\n        prepareHeaders(bytesWritten);\n        msg = new VertxFullHttpResponse(head, version, status, data, headers, trailingHeaders);\n      } else {\n        msg = new VertxLastHttpContent(data, trailingHeaders);\n      }\n      conn.write(msg, listener);\n      if (bodyEndHandler != null) {\n        bodyEndHandler.handle(null);\n      }\n      if (!closed && endHandler != null) {","sourceCodeStart":381,"sourceCodeEnd":417,"githubUrl":"https://github.com/eclipse-vertx/vert.x/blob/fb308bd8c3f12c79f4ae89bef67fadf6c80d036e/vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1ServerResponse.java#L381-L417","documentation":"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.","triggerScenarios":"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.","commonSituations":"Async flows where both an error callback and success callback call end; middleware plus handler each ending the response; retry logic around end().","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)"],"exampleFix":"// before\nif (err != null) { response.end(\"err\"); }\nresponse.end(result);\n// after\nif (err != null) { response.end(\"err\"); return; }\nresponse.end(result);","handlingStrategy":"validation","validationCode":"// guard with a flag or check ended state via the response future\nif (!ended.get()) { ended.set(true); response.end(data); }","typeGuard":null,"tryCatchPattern":"try { response.end(data); } catch (IllegalStateException e) { /* response already ended */ }","preventionTips":["Return immediately after end()","Use an AtomicBoolean to make end idempotent","Ensure only one code path completes the response"],"tags":["http","response","lifecycle","http1"],"backgroundTag":"invalid-state-transition","analyzedSha":"fb308bd8c3f12c79f4ae89bef67fadf6c80d036e","analyzedAt":"2026-09-06T11:37:12.241Z","contentChangedAt":"2026-09-06T11:37:12.241Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}