Hi
after upgrading to spring boot 3.2.4 from 3.2.3 our applications start logging this exception over and over (I suspect on every request):
org.springframework.web.context.request.async.AsyncRequestNotUsableException: ServletOutputStream failed to write: java.io.IOException: Connection reset by peer
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleHttpServletResponse.handleIOException(StandardServletAsyncWebRequest.java:320)
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleServletOutputStream.write(StandardServletAsyncWebRequest.java:378)
at org.springframework.util.StreamUtils$NonClosingOutputStream.write(StreamUtils.java:264)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator._flushBuffer(UTF8JsonGenerator.java:2203)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator.writeString(UTF8JsonGenerator.java:526)
at nl.collectcar.api.book.model.dto.serialize.JsonUuidSerializer.serialize(JsonUuidSerializer.java:15)
at nl.collectcar.api.book.model.dto.serialize.JsonUuidSerializer.serialize(JsonUuidSerializer.java:12)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:772)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:772)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:772)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:479)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:399)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1568)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1061)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:483)
at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:114)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:297)
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:245)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:136)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:925)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:830)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.servlet.v6_0.OpenTelemetryHandlerMappingFilter.doFilter(OpenTelemetryHandlerMappingFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:735)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at java.base/java.lang.VirtualThread.run(VirtualThread.java:309)
Suppressed: org.springframework.web.context.request.async.AsyncRequestNotUsableException: Response not usable after response errors.
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleHttpServletResponse.obtainLockAndCheckState(StandardServletAsyncWebRequest.java:314)
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleServletOutputStream.write(StandardServletAsyncWebRequest.java:373)
at org.springframework.util.StreamUtils$NonClosingOutputStream.write(StreamUtils.java:264)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator._flushBuffer(UTF8JsonGenerator.java:2203)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator.close(UTF8JsonGenerator.java:1227)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:452)
... 56 common frames omitted
Caused by: org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:344)
at org.apache.catalina.connector.OutputBuffer.flushByteBuffer(OutputBuffer.java:773)
at org.apache.catalina.connector.OutputBuffer.append(OutputBuffer.java:676)
at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:379)
at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:357)
at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:97)
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleServletOutputStream.write(StandardServletAsyncWebRequest.java:375)
... 84 common frames omitted
Caused by: java.io.IOException: Connection reset by peer
at java.base/sun.nio.ch.SocketDispatcher.write0(Native Method)
at java.base/sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:62)
at java.base/sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:137)
at java.base/sun.nio.ch.IOUtil.write(IOUtil.java:102)
at java.base/sun.nio.ch.IOUtil.write(IOUtil.java:58)
at java.base/sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:542)
at org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:118)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doWrite(NioEndpoint.java:1381)
at org.apache.tomcat.util.net.SocketWrapperBase.doWrite(SocketWrapperBase.java:764)
at org.apache.tomcat.util.net.SocketWrapperBase.writeBlocking(SocketWrapperBase.java:589)
at org.apache.tomcat.util.net.SocketWrapperBase.write(SocketWrapperBase.java:533)
at org.apache.coyote.http11.Http11OutputBuffer$SocketOutputBuffer.doWrite(Http11OutputBuffer.java:540)
at org.apache.coyote.http11.filters.ChunkedOutputFilter.doWrite(ChunkedOutputFilter.java:112)
at org.apache.coyote.http11.Http11OutputBuffer.doWrite(Http11OutputBuffer.java:193)
at org.apache.coyote.Response.doWrite(Response.java:616)
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:331)
... 90 common frames omitted
this did not happen with the 3.2.3 version and after a rollback the exceptions are no longer logged.
The exceptions themselves do not seem to have any functional impact.
Used JVM: Amazon Corretto JDK 21.0.2
Best regards Vasil
Comment From: wilkinsona
The change in behavior is due to https://github.com/spring-projects/spring-framework/issues/32340. We'll transfer this to Spring Framework so that they can investigate further.
Comment From: rstoyanchev
What's actually logging the exception?
Previously, this would have been org.apache.catalina.connector.ClientAbortException
. It is now wrapped with AsyncRequestNotUsableException
, and that should be handled in DefaultHandlerExceptionResolver by marking it resolved without further handling.
Comment From: vap78
Hi
looks like this is traced in a custom exception handler annotated with @ControllerAdvice.
What is the recommendation here?
I could simply ignore this specific exception, but what would be the performance impact of exceptions being generated/thrown on every request?
Best regards Vasil
Comment From: rstoyanchev
In this case the exception only wraps the IOException
from the Servlet response. In other words there is already an exception, and such exceptions should be raised only when something goes wrong with the network, not for every request.
The exception implies the response is no longer usable, and you can't send any more data. You can treat it in the same way that you would the underlying ClientAbortException
or any IOException
from the response.
I'm closing as there is nothing for us to do, but feel free to comment if necessary.
Comment From: hw207165
Hi
looks like this is traced in a custom exception handler annotated with @ControllerAdvice.
What is the recommendation here?
I could simply ignore this specific exception, but what would be the performance impact of exceptions being generated/thrown on every request?
Best regards Vasil
How did you solve it?I am facing the same problem. Thanks.
Comment From: danwashusen
We are also seeing this error after an upgrade, it doesn't seem to happen for every request, just those that have used response.sendRedirect(...)
. In our case we just see 'Response not usable after response errors.' logged with a nasty error message (no IO errors)... the client gets the redirect, so the issue is only server side...
Comment From: vap78
Hi looks like this is traced in a custom exception handler annotated with @ControllerAdvice. What is the recommendation here? I could simply ignore this specific exception, but what would be the performance impact of exceptions being generated/thrown on every request? Best regards Vasil
How did you solve it?I am facing the same problem. Thanks.
We didn't .
The only solution I see is to suppress the exception in the custom exception handler. But I don't understand, why is it being thrown as we did not changes, except the Spring boot upgrade.
Comment From: vap78
From what I see what has changed is the exception type thrown.
Before spring-web:6.1.5 a ClientAbortException was thrown. Since 6.1.5 a AsyncRequestNotUsableException.
So adding AsyncRequestNotUsableException to the exception handler will restore the old state.
Comment From: namannigam
We faced a similar issue. From this thread, I am taking away that we would either have to handle the AsyncRequestNotUsableException
similar to other exceptions in the ControllerAdvice
or have to live with it and look for resolution as any other IOException. Is that right @vap78 @rstoyanchev ?
Comment From: vap78
@namannigam never tried it. Sounds reasonable for APIs that are not async by nature. But I'm not the expert here.
Comment From: rstoyanchev
@namannigam yes. The main change is that AsyncRequestNotUsableExcepton
wraps the underlying IOException
that came from the Servlet container either while trying to use the response or as an onError callback in an async request. If you check what the wrapped exception is, e.g. ClientAbortException
, you probably have handling for it already.
Comment From: mauriciogeneroso
@rstoyanchev I got the same issue after upgrading Spring for a higher version than 3.2.3
.
If this is a wrapper only and a change from ClientAbortException
to AsyncRequestNotUsableExcepton
, why won't we able to see ClientAbortException
before? We were not handling ClientAbortException
and I don't think is correct to make clients of the framework to start handling AsyncRequestNotUsableExcepton
Comment From: rstoyanchev
It's difficult to say without more details. The exception can occur in a wide range of scenarios, writing to the response. Also, check the cause, if it is a different exception perhaps. Tomcat may raise one of several IOException
's. Or maybe you have handling for IOException
.
Comment From: SiwyDym
I encountered issue with 3.2.6, there is some mitigation for that?
Comment From: EAlf91
@SiwyDym saw some mitigations using a controller advice ignoring it. just leave the method empty and it won't log anything
@ControllerAdvice
public class CustomAdvice {
@ExceptionHandler(AsyncRequestNotUsableException.class)
void handleAsyncRequestNotUsableException(AsyncRequestNotUsableException e) { }
}
Comment From: rstoyanchev
We do have handling for AsyncRequestNotUsableException
in DefaultHandlerExceptionResolver
. I am guessing however that you have an @ExceptionHandler
method that handles any Exception
or IOException
, and that is called before DefaultHandlerExceptionResolver
has had a chance.
I've created #33225 to add default handling in ResponseEntityExceptionHandler
as well, which increases the chance that the exception will be handled out of the box as a no-op by default.
Comment From: SiwyDym
Okay, thank You. I have a slightly different situation because I got these exceptions when using SSE so I also need to suppress IllegalStateException
with specific error message in the AsyncTaskExecutor
, quite clumsy but works.
Comment From: rstoyanchev
@SiwyDym could you elaborate on your scenario a little more? You shouldn't have to handle exceptions in SSE, and there isn't much you can do for handling. I'm also unsure how IllegalStateException relates to this. Perhaps a small code snippet that shows example handling.
Comment From: SiwyDym
I try to make some demo repository, but without success. The setup is like
///config scheduler
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setThreadNamePrefix("XXX-");
threadPoolTaskScheduler.setErrorHandler(t -> {
//FIXME: remove it after SB fix it, probably this issue: https://github.com/spring-projects/spring-framework/issues/33225
if (t instanceof IllegalStateException
&& (t.getMessage().equals("A non-container (application) thread attempted to use the AsyncContext after an " +
"error had occurred and the call to AsyncListener.onError() had returned. This is not allowed to avoid race conditions.")
|| t.getMessage().equals("The request associated with the AsyncContext has already completed processing."))
) {
log.debug("Please ignore below error, because it's inner issue in SB,", t);
}
});
return threadPoolTaskScheduler;
}
///// controller
@GetMapping(value = "/xxx/summary", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<List<XXXSummary>> xxxSummaryFlux() {
return fluxSink.asFlux().map(e -> xxxManager.getxxxSummary());
}
@ControllerAdvice
@Slf4j
static class CustomAdvice {
@ExceptionHandler(AsyncRequestNotUsableException.class)
void handleAsyncRequestNotUsableException(AsyncRequestNotUsableException e) {
//FIXME: remove it after SB fix it, probably this issue: https://github.com/spring-projects/spring-framework/issues/33225
log.debug("Please ignore below error, because it's inner issue in SB,", e);
}
}
///flux config
@Configuration
public class FluxConfig {
@Bean
Sinks.Many<Object> fluxSink() {
return Sinks.many().multicast().directBestEffort();
}
@Bean
FluxNotifier fluxNotifier(Sinks.Many<Object> fluxSink) { //used to trigger change
return new FluxNotifier(fluxSink);
}
}
It sometimes entered this new @ExceptionHandler(AsyncRequestNotUsableException.class)
, but it also throw in the executor ISE. During debugging the cause is the same as for @ExceptionHandler(AsyncRequestNotUsableException.class)
. Unfortunately I'm totally swamped, but in free time try to make demo-project for that
Comment From: rstoyanchev
Thanks for the extra context @SiwyDym. The change in #33225 is now in, and you should be able to remove your own @ExceptionHandler
. I'm not sure that it will help with the IllegalStateException
though, and I don't see from the snippet that how the ThreadPoolTaskScheduler
is used. Feel free to try with 6.2.0-SNAPSHOT
. If you manage to create a reproducer with the IllegalStateException
, we'll take a look. You can also try upgrading to the latest Tomcat maintenance version to see if it helps.
Comment From: berkaygiris
Is this fix available with a spring boot release?
Comment From: bclozel
@berkaygiris see #33225. This will be released with Framework 6.2 and Spring Boot 3.4.