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
- 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
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
- 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
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)