{"record":{"id":"0683f8c9e8077e54","repo":"eclipse-vertx/vert.x","slug":"response-has-already-been-written","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/HttpServerResponseImpl.java","lineNumber":398,"sourceCode":"        future = stream.writeHeaders(trailedMap, true);\n      }\n      Handler<Void> bodyEndHandler = this.bodyEndHandler;\n      Handler<Void> endHandler = this.endHandler;\n      if (bodyEndHandler != null) {\n        bodyEndHandler.handle(null);\n      }\n      if (endHandler != null) {\n        endHandler.handle(null);\n      }\n    }\n    return future;\n  }\n\n  private Future<Void> write_(Buffer chunk, boolean end) {\n    boolean sendHeaders;\n    synchronized (conn) {\n      if (ended) {\n        throw new IllegalStateException(\"Response has already been written\");\n      }\n      ended = end;\n      if (end && !headWritten && requiresContentLengthHeader()) {\n        headers().set(HttpHeaderNames.CONTENT_LENGTH, chunk == null ? \"0\" : HttpUtils.positiveLongToString(chunk.length()));\n      }\n      sendHeaders = prepareHeaders();\n    }\n    if (sendHeaders) {\n      return stream.writeHead(new HttpResponseHead(status.code(), status.reasonPhrase(), headersMap), chunk, end);\n    } else {\n      return stream.writeChunk(chunk, end);\n    }\n  }\n\n  private boolean requiresContentLengthHeader() {\n    return requestMethod != HttpMethod.HEAD && status != HttpResponseStatus.NOT_MODIFIED && !headersMap.contains(HttpHeaderNames.CONTENT_LENGTH);\n  }\n","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/eclipse-vertx/vert.x/blob/fb308bd8c3f12c79f4ae89bef67fadf6c80d036e/vertx-core/src/main/java/io/vertx/core/http/impl/HttpServerResponseImpl.java#L380-L416","documentation":"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.","triggerScenarios":"Calling write() or end() after end() already succeeded; multiple end() calls; writing from a callback that fires after another code path ended the response.","commonSituations":"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.","solutions":["Ensure exactly one write/end path executes — guard with a boolean or use response.ended() if available","In error handlers, check whether the response is already ended before attempting to write an error response","Centralize response completion in a single helper that is idempotent","Structure async flows with flatMap/transform so only one completion path runs"],"exampleFix":"// before\nresponse.end(\"ok\");\nonError(err -> response.end(\"error\")); // IllegalStateException if success path ran\n\n// after\nAtomicBoolean done = new AtomicBoolean();\nvoid respond(String body) {\n  if (done.compareAndSet(false, true)) response.end(body);\n}","handlingStrategy":"try-catch","validationCode":"if (!response.ended()) {\n  response.end(body);\n}","typeGuard":null,"tryCatchPattern":"try {\n  response.end(chunk);\n} catch (IllegalStateException e) {\n  // response already written; skip\n}","preventionTips":["Ensure a single completion path per response","Check response.ended() in error handlers before writing","Use idempotent response helpers with a done flag"],"tags":["http","response","illegal-state","async"],"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-14T00:17:10.932Z"}