Spring-Web v6.2.3 of introduced changes within WebAsyncManager
to wrap client disconnection errors in #34363, but these changes within the throwable consumer are not null-safe as DisconnectedClientHelper.isClientDisconnectedException(ex)
never checks if the exception is null. This causes a NullPointerException when executing NestedExceptionUtils.getMostSpecificCause(ex).getMessage()
within isClientDisconnectedException()
.
Mentioned code changes
Maybe the error was introduced by the incorrect JavaDoc of NestedExceptionUtils.getMostSpecificCause(Throwable original)
, which can in fact return null if the original exception was also null.
This NPE does not affect the behavior for the client, but causes a lot of warn-logs, as the NPE is caught by Tomcats AsyncContextImpl
:
2025-03-04T12:00:34.175Z WARN 1 --- [springboot-application-xyz] [nio-7210-exec-4] o.apache.catalina.core.AsyncContextImpl : onError() call failed for listener of type [org.apache.catalina.core.AsyncListenerWrapper]
java.lang.NullPointerException: Cannot invoke "java.lang.Throwable.getMessage()" because the return value of "org.springframework.core.NestedExceptionUtils.getMostSpecificCause(java.lang.Throwable)" is null
at org.springframework.web.util.DisconnectedClientHelper.isClientDisconnectedException(DisconnectedClientHelper.java:120) ~[spring-web-6.2.3.jar!/:6.2.3]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startDeferredResultProcessing$6(WebAsyncManager.java:450) ~[spring-web-6.2.3.jar!/:6.2.3]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.lambda$onError$0(StandardServletAsyncWebRequest.java:195) ~[spring-web-6.2.3.jar!/:6.2.3]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) ~[na:na]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.onError(StandardServletAsyncWebRequest.java:195) ~[spring-web-6.2.3.jar!/:6.2.3]
at org.apache.catalina.core.AsyncListenerWrapper.fireOnError(AsyncListenerWrapper.java:49) ~[tomcat-embed-core-10.1.36.jar!/:na]
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:415) ~[tomcat-embed-core-10.1.36.jar!/:na]
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:242) ~[tomcat-embed-core-10.1.36.jar!/:na]
at org.apache.coyote.AbstractProcessor.dispatch(AbstractProcessor.java:243) ~[tomcat-embed-core-10.1.36.jar!/:na]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:57) ~[tomcat-embed-core-10.1.36.jar!/:na]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905) ~[tomcat-embed-core-10.1.36.jar!/:na]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) ~[tomcat-embed-core-10.1.36.jar!/:na]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.36.jar!/:na]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) ~[tomcat-embed-core-10.1.36.jar!/:na]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.36.jar!/:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) ~[tomcat-embed-core-10.1.36.jar!/:na]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
v6.2.1 and prior are able to correctly process null
values in their error handler implemented in WebAsyncManager
, so I assume this is an unwanted side effect.