Tencent/VasSonic · error · IllegalStateException

getWriter() has already been called on this response.

Error message

getWriter() has already been called on this response.

What it means

HttpServletResponseCopier is a response wrapper that captures response bodies. Per the Servlet spec, getOutputStream() and getWriter() are mutually exclusive; this wrapper enforces that by throwing IllegalStateException if getWriter() was already called.

Solutions

  1. Standardize on one access method per response: use getWriter() for text or getOutputStream() for binary, never both
  2. In filters, unwrap/replace the wrapper per branch so each stage has a consistent accessor
  3. Track which accessor was used and route subsequent writes through it
  4. If copying content, ensure the wrapper is created fresh for each response dispatch (include/forward)

Example fix

// before
response.getWriter().write("log");
response.getOutputStream().write(bytes); // IllegalStateException
// after
PrintWriter w = response.getWriter();
w.write("log");
w.flush(); // stick to a single accessor for the whole response
Defensive patterns

Strategy: try-catch

Validate before calling

// track accessor usage before writing
if (writerAlreadyObtained) {
  use getWriter() path instead of getOutputStream();
}

Try / catch

try {
  ServletOutputStream out = responseCopier.getOutputStream();
  out.write(bytes);
} catch (IllegalStateException e) {
  // fall back to writing bytes through the Writer
  responseCopier.getWriter().write(new String(bytes, StandardCharsets.ISO_8859_1));
}

Prevention

When it happens

Trigger: Calling getOutputStream() on the wrapped response after getWriter() has already been called on the same wrapper instance, e.g. a filter chain where one component writes text via getWriter() and a later one writes bytes via getOutputStream().

Common situations: Servlet filters that wrap the response and then delegate to servlets/frameworks with differing writing conventions; mixing JSP (getWriter) with binary output (getOutputStream); double-wrapping responses in nested filters.

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/83ec98d015f23295. Report an issue: GitHub.

Appendix: source

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

import java.io.OutputStreamWriter;
import java.io.PrintWriter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class HttpServletResponseCopier extends HttpServletResponseWrapper {
    private PrintWriter writer;
    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

View on GitHub (pinned to 59936beff6)