{"id":"3bb45247e1350c9a","repo":"gin-gonic/gin","slug":"gin-response-body-already-written","errorCode":null,"errorMessage":"gin: response body already written","messagePattern":"gin: response body already written","errorType":"exception","errorClass":"errHijackAlreadyWritten","httpStatus":null,"severity":"error","filePath":"response_writer.go","lineNumber":20,"sourceCode":"// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n)\n\nconst (\n\tnoWritten     = -1\n\tdefaultStatus = http.StatusOK\n)\n\nvar errHijackAlreadyWritten = errors.New(\"gin: response body already written\")\n\n// ResponseWriter ...\ntype ResponseWriter interface {\n\thttp.ResponseWriter\n\thttp.Hijacker\n\thttp.Flusher\n\thttp.CloseNotifier\n\n\t// Status returns the HTTP response status code of the current request.\n\tStatus() int\n\n\t// Size returns the number of bytes already written into the response http body.\n\t// See Written()\n\tSize() int\n\n\t// WriteString writes the string into the response body.\n\tWriteString(string) (int, error)\n","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/gin-gonic/gin/blob/34dac209ffb6ef85cc78c5d217bbb7ad001d68fd/response_writer.go#L2-L38","documentation":"errHijackAlreadyWritten is returned by ResponseWriter.Hijack (response_writer.go:115) when Hijack is called after response body bytes (size > 0) have already been written. Once body data is on the wire the connection can no longer be upgraded/hijacked cleanly; Gin permits hijack only before body writes (size == -1) or right after headers (size == 0).","triggerScenarios":"Calling c.Writer.Hijack() after a c.JSON / c.String / c.Write that wrote body bytes; WebSocket upgrade libraries (e.g. github.com/coder/websocket) attempting to upgrade after the handler wrote a partial response; SSE handlers that wrote an event then try to upgrade.","commonSituations":"Mixing a normal HTTP response with a WebSocket upgrade in the same handler; flushing headers + partial body then calling Hijack; middleware that writes an error before delegating to a websocket upgrader.","solutions":["Ensure no body bytes are written before calling Hijack; branch the handler so the upgrade path never writes a normal response.","Move the WebSocket upgrade to the very first thing the handler does, before any c.JSON / c.String.","If you conditionally write, call c.Writer.Written() / c.Writer.Size() and skip writing before attempting Hijack."],"exampleFix":"// before\nc.String(http.StatusOK, \"hi\")\nconn, _, _ := c.Writer.Hijack() // error\n// after\nconn, buf, err := c.Writer.Hijack()\nif err != nil { /* handle */ return }\ndefer conn.Close()\n// do websocket handshake on conn","handlingStrategy":"validation","validationCode":"if c.Writer.Written() || c.Writer.Size() > 0 {\n    return errors.New(\"cannot hijack after body written\")\n}\nconn, buf, err := c.Writer.Hijack()","typeGuard":null,"tryCatchPattern":"conn, buf, err := c.Writer.Hijack()\nif err != nil {\n    if errors.Is(err, errHijackAlreadyWritten) {\n        // response already started; cannot upgrade\n    }\n}","preventionTips":["Branch handlers: a websocket/upgrade path must not write any body before Hijack.","Check c.Writer.Size() == 0 (or Written()==false) before upgrading.","Keep websocket upgrades at the very start of the handler chain."],"tags":["hijack","websocket","response-writer","go"],"analyzedSha":"34dac209ffb6ef85cc78c5d217bbb7ad001d68fd","analyzedAt":"2026-08-04T21:26:18.438Z","schemaVersion":2}