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
- Standardize on one access method per response: use getWriter() for text or getOutputStream() for binary, never both
- In filters, unwrap/replace the wrapper per branch so each stage has a consistent accessor
- Track which accessor was used and route subsequent writes through it
- 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
- Choose one response accessor per request lifecycle at the filter-design stage
- Document that wrapped responses enforce Servlet getWriter/getOutputStream exclusivity
- Avoid frameworks/handlers with mixed output conventions inside the same filter chain
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;
}
@OverrideView on GitHub (pinned to 59936beff6)