{"record":{"id":"52bcb6093e72084f","repo":"eclipse-vertx/vert.x","slug":"response-head-already-sent","errorCode":null,"errorMessage":"Response head already sent","messagePattern":"Response head already sent","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"vertx-core/src/main/java/io/vertx/core/http/impl/HttpServerResponseImpl.java","lineNumber":120,"sourceCode":"    if (handler != null) {\n      handler.handle(cause);\n    }\n  }\n\n  void handleClose(Void v) {\n    Handler<Void> handler;\n    synchronized (conn) {\n      closed = true;\n      handler = closeHandler;\n    }\n    if (handler != null) {\n      handler.handle(null);\n    }\n  }\n\n  private void checkHeadWritten() {\n    if (headWritten) {\n      throw new IllegalStateException(\"Response head already sent\");\n    }\n  }\n\n  @Override\n  public HttpServerResponse exceptionHandler(Handler<Throwable> handler) {\n    synchronized (conn) {\n      if (handler != null) {\n        checkValid();\n      }\n      exceptionHandler = handler;\n      return this;\n    }\n  }\n\n  @Override\n  public int getStatusCode() {\n    synchronized (conn) {\n      return status.code();","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/eclipse-vertx/vert.x/blob/fb308bd8c3f12c79f4ae89bef67fadf6c80d036e/vertx-core/src/main/java/io/vertx/core/http/impl/HttpServerResponseImpl.java#L102-L138","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nresponse.write(\"chunk\");\nresponse.putHeader(\"X-Custom\", \"v\"); // IllegalStateException\n\n// after\nresponse.putHeader(\"X-Custom\", \"v\");\nresponse.write(\"chunk\");","handlingStrategy":"validation","validationCode":"if (!response.headWritten()) {\n  response.setStatusCode(200);\n  response.putHeader(\"X-Custom\", \"v\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  response.putHeader(\"X-Custom\", \"v\");\n} catch (IllegalStateException e) {\n  // head already sent; log instead of mutating\n}","preventionTips":["Set status and headers before the first write/end","Avoid mutating response state in late callbacks","Centralize header configuration before dispatching to handlers"],"tags":["http","response-headers","illegal-state"],"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"}