Spring DeferredResult causes IOException: An established connection was aborted by the software in your host machine

12,907

Solution 1

The reason why it's failing is because when the first user closes the browser the Stream gets closed, and when you try to set the DeferredResult's result spring tries to send it to the client, causing the error.

You should try checking if the DeferredResult is in a usable status by calling it's isSetOrExpired() method before writting the result:

            while(iterator.hasNext()){
                DeferredResult<String> result = iterator.next();
                System.out.println("Setting result.");
                if(!result.isSetOrExpired()){
                    result.setResult("Somebody clicked the thing! Click count: " + clickCount);
                }
                iterator.remove();
            }

If Spring still doesn't get the Deferred canceled when the client closes the connection, then there's not too much to do to prevent the exception from actually happening: Detecting client disconnect in tomcat servlet?.

Note that long-poling and comet in general is a hard thing to build from scratch, you should consider using something like Atmosphere for this. It will give you both websockets and comet for old browser compatibility.

About your other questions

  • What are the best practices for doing this kind of thing, preferably in a way that doesn't generate exceptions?

    • You should consider using WebSockets for this. Spring 4.2 comes with some nice features for it, and also check Atmosphere
    • You can also give a chance to server-sent events
  • Is there a better way to keep track of DeferredResults for long polling?

    • I guess your way is ok if everyone is able to click the button and notify everyone listening. If you want some more targeted behaviour (like specifically notifying a user of his individual events) then you should keep a Map of users and pending long polling DeferredResults.
  • Is there a way around the subsequent getOutputStream() has already been called for this response exceptions, which I gather are caused by the error page of the exception handler?

    • That error happens for the same reason than the first one. As an error has occurred, the servlet container is trying to print the error JSP to the closed stream causing an error. The way to solve this is just making no error to happen ever when a request is closed before getting answered :-).

Solution 2

What is causing the original ClientAbortException? Should I be doing something different?

When we are using DeferredResult or Callable:

ClientAbortException:Client made some other request/clicking some other link. which means current action suspended and it throws ClientAbortException on the current running thread.

An established connection was aborted by the software in your host machine

Most Probably,Windows Firewall can also do this kind of abort functionality check for it once.

What are the best practices for doing this kind of thing, preferably in a way that doesn't generate exceptions?

WebSockets

Is there a better way to keep track of DeferredResults for long polling?

Define a collection for different User along with long polling.

Is there a way around the subsequent getOutputStream() has already been called for this response exceptions, which I gather are caused by the error page of the exception handler?

This may also be a reason for this Current Exception being thrown.

Share:
12,907
Kevin Workman
Author by

Kevin Workman

By day I work for Google. Code 80% of the time, outreach and education 20% of the time. On a good day, the other way around. By night I run a coding tutorial site at HappyCoding.io. Come say hi! On Stack Overflow I mostly lurk in the processing tag and its friends. More info at KevinWorkman.com.

Updated on August 10, 2022

Comments

  • Kevin Workman
    Kevin Workman over 1 year

    I'm trying to use Spring's DeferredResult to perform long polling. In this example, one user visits a page that uses long polling to wait for another user to click a link. A second user (you in another browser) then clicks that link, and the long polling returns to the first user, notifying her of the second user's click.

    The jsp looks like this:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Spring Example</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
        <script>
        function pollContent() {
            $.ajax({url: "waitForClick", success: function(result){
                console.log("Polled result: " + result);
                $("#polledContent").html(result);
                pollContent();
            }});
        }
        $(pollContent);
        </script>
      </head>
    <body>
        <p><a href="clickTheThing">Click this thing.</a></p>
        <p id="polledContent">Waiting for somebody to click the thing...</p>
    </body>
    </html>
    

    And the controller looks like this:

    package com.example.controller;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.stereotype.Component;
    import org.springframework.ui.ModelMap;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.context.request.async.DeferredResult;
    
    import com.example.controller.interfaces.ExampleControllerInterface;
    
    @Component
    public class ExampleController implements ExampleControllerInterface{
    
        private int clickCount = 0;
    
        private List<DeferredResult<String>> waiting = new ArrayList<>();
    
        @Override
        public String viewHomePage(HttpServletRequest request, ModelMap model, HttpSession session){
            return "index";
        }
    
        @RequestMapping(value = "/clickTheThing", method = RequestMethod.GET)
        public String clickTheThing(HttpServletRequest request, ModelMap model, HttpSession session){
    
            new Thread(){
                public void run(){
    
                    clickCount++;
                    System.out.println("Somebody clicked the thing! Click count: " + clickCount);
                    Iterator<DeferredResult<String>> iterator = waiting.iterator();
                    while(iterator.hasNext()){
                        DeferredResult<String> result = iterator.next();
                        System.out.println("Setting result.");
                        result.setResult("Somebody clicked the thing! Click count: " + clickCount);
                        iterator.remove();
                    }
                }
            }.start();
    
            return "clicked";
        }
    
        @ResponseBody
        @RequestMapping(value = "/waitForClick", method = RequestMethod.GET)
        public DeferredResult<String> waitForClick(HttpServletRequest request, ModelMap model, HttpSession session){
            final DeferredResult<String> result = new DeferredResult<>();
            waiting.add(result);
            return result;
        }
    
        @ResponseBody
        @RequestMapping(value = "/getClickCount", method = RequestMethod.GET)
        public String getClickCount(HttpServletRequest request, ModelMap model, HttpSession session){
            return String.valueOf(clickCount);
        }
    }
    

    And for completeness, here is my ErrorConfig class:

    package com.example.config;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    
    @ControllerAdvice
    public class ErrorConfig{
    
        @ExceptionHandler(Exception.class)
        public String handleException (HttpServletRequest request, HttpServletResponse response, HttpSession session, Exception e) {
            e.printStackTrace();
            return "index";
        }
    }
    

    This seems to work okay. The first user is indeed notified whenever another user clicks the link.

    However, if that first user refreshes the page before the second user clicks the link, I also get a stack trace for every "old" DeferredResult:

    org.apache.catalina.connector.ClientAbortException: java.io.IOException: An established connection was aborted by the software in your host machine
        at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:396)
        at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:426)
        at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:342)
        at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:316)
        at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:110)
        at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:297)
        at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141)
        at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229)
        at org.springframework.util.StreamUtils.copy(StreamUtils.java:106)
        at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:106)
        at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:40)
        at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:208)
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:143)
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:89)
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:193)
        at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:71)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:122)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:689)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721)
        at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:639)
        at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:605)
        at org.apache.catalina.core.AsyncContextImpl$1.run(AsyncContextImpl.java:208)
        at org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:363)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:78)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
        at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:405)
        at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1636)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:646)
        at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)
    Caused by: java.io.IOException: An established connection was aborted by the software in your host machine
        at sun.nio.ch.SocketDispatcher.write0(Native Method)
        at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51)
        at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
        at sun.nio.ch.IOUtil.write(IOUtil.java:65)
        at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:470)
        at org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:122)
        at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:101)
        at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:173)
        at org.apache.coyote.http11.InternalNioOutputBuffer.writeToSocket(InternalNioOutputBuffer.java:139)
        at org.apache.coyote.http11.InternalNioOutputBuffer.addToBB(InternalNioOutputBuffer.java:197)
        at org.apache.coyote.http11.InternalNioOutputBuffer.access$000(InternalNioOutputBuffer.java:41)
        at org.apache.coyote.http11.InternalNioOutputBuffer$SocketOutputBuffer.doWrite(InternalNioOutputBuffer.java:320)
        at org.apache.coyote.http11.filters.IdentityOutputFilter.doWrite(IdentityOutputFilter.java:84)
        at org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:257)
        at org.apache.coyote.Response.doWrite(Response.java:523)
        at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:391)
        ... 50 more
    Aug 20, 2015 7:19:24 PM org.apache.catalina.core.ApplicationDispatcher invoke
    SEVERE: Servlet.service() for servlet jsp threw exception
    java.lang.IllegalStateException: getOutputStream() has already been called for this response
        at org.apache.catalina.connector.Response.getWriter(Response.java:535)
        at org.apache.catalina.connector.ResponseFacade.getWriter(ResponseFacade.java:212)
        at javax.servlet.ServletResponseWrapper.getWriter(ServletResponseWrapper.java:103)
        at org.apache.jasper.runtime.JspWriterImpl.initOut(JspWriterImpl.java:115)
        at org.apache.jasper.runtime.JspWriterImpl.flushBuffer(JspWriterImpl.java:108)
        at org.apache.jasper.runtime.PageContextImpl.release(PageContextImpl.java:173)
        at org.apache.jasper.runtime.JspFactoryImpl.internalReleasePageContext(JspFactoryImpl.java:120)
        at org.apache.jasper.runtime.JspFactoryImpl.releasePageContext(JspFactoryImpl.java:75)
        at org.apache.jsp.WEB_002dINF.jsp.index_jsp._jspService(index_jsp.java:93)
        at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
        at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:432)
        at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:403)
        at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:347)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721)
        at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:584)
        at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:523)
        at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:201)
        at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:267)
        at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1221)
        at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1005)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:952)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721)
        at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:639)
        at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:605)
        at org.apache.catalina.core.AsyncContextImpl$1.run(AsyncContextImpl.java:208)
        at org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:363)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:78)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
        at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:405)
        at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1636)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:646)
        at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)
    

    I can simply ignore these exceptions, but that feels wrong.

    So, my questions are:

    • What is causing the original ClientAbortException? Should I be doing something different?
    • What are the best practices for doing this kind of thing, preferably in a way that doesn't generate exceptions?
    • Is there a better way to keep track of DeferredResults for long polling?
    • Is there a way around the subsequent getOutputStream() has already been called for this response exceptions, which I gather are caused by the error page of the exception handler?

    I have a mavenized version of this project available on GitHub here if you want to try it yourself.

    In the end I'm trying to add a notifications system to my Spring website, similar to StackOverflow's notification system. If there's a better way to do that with Spring and long polling, I'm all ears.

    Edit: I haven't received any answers (or even comments), so I've added a bounty. I'd definitely appreciate any feedback!

    • V G
      V G over 8 years
      As an alternative on how to do that in Spring: I would use WebSockets.
    • Kevin Workman
      Kevin Workman over 8 years
      Thanks Andrei. I see that StackOverflow uses WebSockets, so I suppose they're worth investigating. I was just really hoping to get it working with DeferredResult. Good to have a Plan B though.
    • user207421
      user207421 over 8 years
      The ClientAbortException was caused by the IOException, as the stack trace clearly says, and there are numerous existing questions here about that.
    • Kevin Workman
      Kevin Workman over 8 years
      @EJP I've seen those questions, but none of them seem to match my scenario. I'd be happy to check out any specific question you think explains my problem.
    • sven.kwiotek
      sven.kwiotek over 8 years
      I could not reproduce your error. Please could you explain in more detail the actions from User1 and User2? I only see Timeout error if User1 is waiting for click from User2 but User2 do nothing.
    • Kevin Workman
      Kevin Workman over 8 years
      @s.kwiotek Make sure you have the ErrorConfig properly set up. Without it, the error is just silently eaten. Here are the steps I would take to repeate the error: User 1 visits the index. (This creates a DeferredResult that gets stored.) User 1 refreshes. (This creates another DeferredResult that gets stored.) User 1 refreshes again. (Another DeferredResult.) User 2 visits the index. User 2 clicks the "Click this thing" link. The last DeferredResult returns correctly, but the first 2 throw errors.
    • sven.kwiotek
      sven.kwiotek over 8 years
      Wich Tomcat Version you work with?
    • Kevin Workman
      Kevin Workman over 8 years
      @s.kwiotek I've been working with Tomcat 8.
    • Alfonso Presa
      Alfonso Presa over 8 years
      I've tried to reproduce the issue with cleans instalations of tomcat 8.0.1 and tomcat 8.0.26... Wich exact version are yo trying against? Also have you made any modifications to the tomcat configuration? If so, wich changes? can you try with a crean 8.0.26 install?
    • Kevin Workman
      Kevin Workman over 8 years
      @AlfonsoPresa I'll check all of that when I get home later today, but make sure you have the ErrorConfig set up properly. Without it, this error is simply ignored.
    • Alfonso Presa
      Alfonso Presa over 8 years
      I can confirm the ErrorConfig is loaded (I added a trace in it's constructor) and that nothing is appearing after: opening two browsers at '/', refreshing the first browser, clicking 'it' in the second one.
    • Alfonso Presa
      Alfonso Presa over 8 years
      "An established connection was aborted by the software in your host machine". I'm strating to think it might be OS releated. I'm on windoze, where are you?
    • Kevin Workman
      Kevin Workman over 8 years
      @AlfonsoPresa Same. Windows 7. But it also happens on a linux server.
    • Alfonso Presa
      Alfonso Presa over 8 years
      That's what I'm using... Then I've no clue why it's working fine in my env... may be it's browser related. Anyway thinking about this, the client suddenly closed the connection with the server... that's an error as proven by the SO link in my answer and there's no way I can think of to avoid it (other than some dirty synchronous Ajax request in the onbeforeunload event). If it's hidden by default unless you add the ErrorConfig, then I guess you should ignore it... Anyway, I strongly recommend you to try Atmosphere to solve this.
    • approxiblue
      approxiblue over 8 years
      Your code is not thread-safe. You need an AtomicInteger instead of int, a concurrent queue instead of an array list, a periodic task to process the queue instead of spawning threads yourself. You should check out this sample MVC project from Spring with examples of DeferredResult.
    • Kevin Workman
      Kevin Workman over 8 years
      @approxiblue Does the thread safety have anything to do with the problem I described?
    • approxiblue
      approxiblue over 8 years
      Not really, just some related improvements.
  • Kevin Workman
    Kevin Workman over 8 years
    Thanks for the reply. Unfortunately, I added the check for isSetOrExpired(), and I still get the error. In fact, isSetOrExpired() always seems to return false no matter what.
  • Alfonso Presa
    Alfonso Presa over 8 years
    Sorry for that... I though Spring will update the state of the DeferredResult if client cancels the request. I updated the response with some more info.