{"record":{"id":"9994b819dc816226","repo":"Tencent/VasSonic","slug":"getoutputstream-has-already-been-called-on-this","errorCode":null,"errorMessage":"getOutputStream() has already been called on this response.","messagePattern":"getOutputStream\\(\\) has already been called on this response\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"sonic-java/src/main/java/com/github/tencent/HttpServletResponseCopier.java","lineNumber":31,"sourceCode":"    private ServletOutputStreamCopier copier;\n\n    public HttpServletResponseCopier(HttpServletResponse response) throws IOException {\n        super(response);\n    }\n\n    @Override\n    public ServletOutputStream getOutputStream() throws IOException {\n        if (writer != null) {\n            throw new IllegalStateException(\"getWriter() has already been called on this response.\");\n        }\n        copier = new ServletOutputStreamCopier();\n        return copier;\n    }\n\n    @Override\n    public PrintWriter getWriter() throws IOException {\n        if (copier != null) {\n            throw new IllegalStateException(\"getOutputStream() has already been called on this response.\");\n        }\n        if (writer == null) {\n            copier = new ServletOutputStreamCopier();\n            writer = new PrintWriter(new OutputStreamWriter(copier, getResponse().getCharacterEncoding()), true);\n        }\n        return writer;\n    }\n\n    @Override\n    public void flushBuffer() throws IOException {\n        if (writer != null) {\n            writer.flush();\n        } else if (copier != null) {\n            copier.flush();\n        }\n    }\n\n    public byte[] getCopy() {","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/Tencent/VasSonic/blob/59936beff656d4b5718ff6444d6c5e001a2c5231/sonic-java/src/main/java/com/github/tencent/HttpServletResponseCopier.java#L13-L49","documentation":"Mirror case of the getOutputStream() guard: HttpServletResponseCopier.getWriter() throws IllegalStateException if getOutputStream() was already called on the wrapper, since the Servlet API forbids mixing the two output accessors on one response.","triggerScenarios":"Calling getWriter() after getOutputStream() on the same HttpServletResponseCopier — typically when a filter or interceptor writes binary/byte data first and then something in the chain (e.g. error page, JSP, charset writer) tries to obtain a Writer.","commonSituations":"Error handling that renders an HTML error page via getWriter() after the handler already streamed bytes; frameworks that internally call getOutputStream() (e.g. for content) before custom text output; logging wrappers that copy the body via output stream then call getWriter().","solutions":["Pick one accessor for the entire request lifecycle and use it consistently","Write text through an OutputStreamWriter over getOutputStream() instead of calling getWriter() when bytes may already have been written","Ensure error pages/forwards use the same accessor as the original handler","Create a fresh wrapper for forwarded/included dispatches rather than reusing one already written to"],"exampleFix":"// before\nresponse.getOutputStream().write(bytes);\nresponse.getWriter().write(msg); // IllegalStateException\n// after\nServletOutputStream out = response.getOutputStream();\nout.write(bytes);\nout.write(msg.getBytes(response.getCharacterEncoding())); // same accessor","handlingStrategy":"try-catch","validationCode":"// before calling getWriter(), confirm no binary output has occurred\nif (copierStreamAlreadyUsed) {\n  write text via OutputStreamWriter over the existing output stream;\n}","typeGuard":null,"tryCatchPattern":"try {\n  PrintWriter w = responseCopier.getWriter();\n  w.write(msg);\n} catch (IllegalStateException e) {\n  // getOutputStream() already used: encode text to bytes on that stream instead\n}","preventionTips":["Render error pages with the same accessor the handler used","Never mix JSP (getWriter) rendering after binary streaming on one wrapped response","Centralize response writing in one utility so the accessor choice is explicit"],"tags":["servlet","java","illegal-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"59936beff656d4b5718ff6444d6c5e001a2c5231","analyzedAt":"2026-09-08T10:27:05.448Z","contentChangedAt":"2026-09-08T10:27:05.448Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}