I have a standard websocket endpoint based on Tyrus implementation which times to times triggers the java.lang.IllegalStateException: Cannot set WriteListener for non-
java.lang.IllegalStateException: Cannot set WriteListener for non-async or non-upgrade request
In order for a request to be fully asynchronous, any Filter in the request-response chain must explicitly be set to support asynchronous requests. Specifically those "catch-all" filters which are mapped on /*
.
In case the filter is registered via <filter>
entry in web.xml
, this can be done by setting child element <async-supported>
to true
.
<filter>
...
<async-supported>true</async-supported>
</filter>
In case the filter is registered via @WebFilter annotation, this can be done by setting its asyncSupported
attribute to true
.
@WebFilter(..., asyncSupported="true")
In case the filter is registered via ServletContext#addFilter(), this can be done by setting Registration.Dynamic#setAsyncSupported(). to true
.
Dynamic filter = servletContext.addFilter(name, type);
filter.setAsyncSupported(true);
Reason is, the WebSocket implementation internally uses ServletRequest#startAsync() during the handshake request in order to keep the request-response pipeline "forever" open until the response is explicitly closed. Its javadoc says the following:
Throws
IllegalStateException
- if this request is within the scope of a filter or servlet that does not support asynchronous operations (that is,isAsyncSupported()
returnsfalse
), or if this method is called again without any asynchronous dispatch (resulting from one of theAsyncContext.dispatch()
methods), is called outside the scope of any such dispatch, or is called again within the scope of the same dispatch, or if the response has already been closed
The isAsyncSupported()
defaults to false
in order to not break existing web applications using poorly implemented servlet filters. Technically, it should suffice to mark only the target Servlet
as async supported and leave the filters alone. A sane "catch-all" Filter
wouldn't explicitly write anything to the HTTP response, but the Servlet API did never forbid that and thus such filters can unfortunately exist.
In case you have one such filter, then you should either fix it to not write anything to the response anymore so that you can safely mark it to support async requests, or to adjust its URL pattern to not cover WebSocket requests. I.e. don't map it on /*
anymore.