0

Summary

We're seeing an intermittent IllegalArgumentException during HTTP response serialization using Spring Boot 3.4.2 on Kubernetes. The exact same image sometimes works perfectly, and other times fails with this error:

java.lang.IllegalArgumentException: getWriter() has already been called for this response 

The exception occurs inside AbstractJackson2HttpMessageConverter.writeInternal(...), which is triggered via the standard message conversion flow when returning a response entity. (https://github.com/spring-projects/spring-framework/blob/d6e35cf1f0dda3b5d2dbc6b26d4975858372982f/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java#L438)

This does not appear to be a race condition in our own code, and we can't consistently reproduce it — but it happens often enough to be problematic (3–4 out of every 10 pod startups).


Stack Trace (Simplified)

The flow leading to the exception looks like this:

HttpEntityMethodProcessor.writeWithMessageConverters(...) → AbstractMessageConverterMethodProcessor (switching converter) → MappingJackson2HttpMessageConverter.write(...) [GENERIC] → AbstractGenericHttpMessageConverter.write(...) → AbstractJackson2HttpMessageConverter.writeInternal(...) ← 💥 Exception thrown here 

When the error does not occur, outputMessage.getBody() returns an empty string. When the error does occur, it fails with the message above.


Details

  • Spring Boot: 3.4.2
  • Java: 21 (Temurin)
  • Kubernetes: multiple environments DEV/TST/UAT were this problem all occurs opon
  • Base image: eclipse-temurin
  • Frameworks used:
    • Spring Security
    • <company>-spring-boot-starter
    • <company>-event-spring-boot-starter
    • <company>-mongo-spring-boot-starter

What We've Observed

  • No suspicious code in our own initialization paths.
  • No relevant @PostConstruct, Flyway, or Liquibase that could interfere.
  • Only one pod is deployed at a time.
  • Pod restarts are sufficient to flip between failure/success.
  • The image is identical across runs.
  • The error only appears during response serialization.

Unfortunately, we can’t run the image locally — we debugged this remotely and verified the issue is inside Spring’s internal message converter flow.


Question

  • Are there known conditions where Spring’s HttpServletResponse or Jackson-based converter may hit this getWriter() conflict intermittently?
  • Could this be related to something in the servlet container initialization (Tomcat, etc.) or Kubernetes startup timing?
  • Any tips to add guards or make this behavior more predictable?

Let me know what else we can share to help isolate the root cause.

Thanks!

Tried

Did a lot of searching..

Verified that the exact same Docker image is used across pod restarts (no code changes).

Remote-debugged the code and confirmed that the correct body is passed to the response, and the error happens deep in Spring’s message conversion layer.

Looked into timing, environment differences, or potential differences in request/response flow — nothing unusual.

Ensured that environment variables and secrets are consistently loaded — no missing values or nulls.

We also cannot reproduce the issue locally — only in Kubernetes — which makes this difficult to isolate.

Expected behavior

We expect the Spring application to return a valid HTTP response body without error, as it does in the majority of cases. Since the body is correctly constructed and the converter is selected appropriately, it should serialize and return the response without throwing an IllegalArgumentException.

1
  • This behavior happens for instance in a JSP when at the end of the control flow the Writer/out is used for outputing the response, but erroneously prior to that some output happens. Could be caused by white space in a tag. And with buffering of JSP output normally is not flushed. So irregularly the error happens; with large output. Commented Apr 3 at 14:15

1 Answer 1

0

This error is thrown in the Response.java. This is logic present in the filterchain of the response. The response.java has two booleans, usingOutputStream and usingWriter. These are false by default.

When getWriter is called, usingWriter is set to true, and the same happens for getOutputStream and usingOutputStream. But here is the trick:

@Override public ServletOutputStream getOutputStream() throws IOException { if (usingWriter) { throw new IllegalStateException(sm.getString("coyoteResponse.getOutputStream.ise")); } usingOutputStream = true; if (outputStream == null) { outputStream = new CoyoteOutputStream(outputBuffer); } return outputStream; } 

Whenever the getOutputStream is called, and usingWriter is true. This exact error is thrown. The exact same happens the otherway around as well, and

getOutputStream() has already been called for this response 

Will be thrown.

The best way to tackle this is to go with the debugger through the Reponse.java class and compare the happyflow with the unhappyflow. When are these usingWriter values set and when are they used.

For me the problem once occured whenever I had a HTTP call within the service that was called via the controller. Recycle() within the Response.java didn't get called, which basicly meant the Response.java was cached until it was reused for a different scenario which causes the error.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.