Tencent/VasSonic · error · IllegalStateException

getOutputStream() has already been called on this response.

Error message

getOutputStream() has already been called on this response.

What it means

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.

Solutions

  1. Pick one accessor for the entire request lifecycle and use it consistently
  2. Write text through an OutputStreamWriter over getOutputStream() instead of calling getWriter() when bytes may already have been written
  3. Ensure error pages/forwards use the same accessor as the original handler
  4. Create a fresh wrapper for forwarded/included dispatches rather than reusing one already written to

Example fix

// before
response.getOutputStream().write(bytes);
response.getWriter().write(msg); // IllegalStateException
// after
ServletOutputStream out = response.getOutputStream();
out.write(bytes);
out.write(msg.getBytes(response.getCharacterEncoding())); // same accessor
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling getWriter(), confirm no binary output has occurred
if (copierStreamAlreadyUsed) {
  write text via OutputStreamWriter over the existing output stream;
}

Try / catch

try {
  PrintWriter w = responseCopier.getWriter();
  w.write(msg);
} catch (IllegalStateException e) {
  // getOutputStream() already used: encode text to bytes on that stream instead
}

Prevention

When it happens

Trigger: 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.

Common situations: 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().

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of Tencent/VasSonic@59936beff6 (2026-09-08). Data as JSON: /api/errors/9994b819dc816226. Report an issue: GitHub.

Appendix: source

Thrown at sonic-java/src/main/java/com/github/tencent/HttpServletResponseCopier.java:31

    private ServletOutputStreamCopier copier;

    public HttpServletResponseCopier(HttpServletResponse response) throws IOException {
        super(response);
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (writer != null) {
            throw new IllegalStateException("getWriter() has already been called on this response.");
        }
        copier = new ServletOutputStreamCopier();
        return copier;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (copier != null) {
            throw new IllegalStateException("getOutputStream() has already been called on this response.");
        }
        if (writer == null) {
            copier = new ServletOutputStreamCopier();
            writer = new PrintWriter(new OutputStreamWriter(copier, getResponse().getCharacterEncoding()), true);
        }
        return writer;
    }

    @Override
    public void flushBuffer() throws IOException {
        if (writer != null) {
            writer.flush();
        } else if (copier != null) {
            copier.flush();
        }
    }

    public byte[] getCopy() {

View on GitHub (pinned to 59936beff6)