Close

Spring MVC - Intercepting Async process lifecycle using DeferredResultProcessingInterceptor

[Last Updated: Aug 20, 2026]

We saw in the last tutorial, how to use CallableProcessingInterceptor to integrate more deeply with async processing lifecycle.

To have similar lifecycle interception around user defined DeferredResult processing, we can register DeferredResultProcessingInterceptor.

Definition of DeferredResultProcessingInterceptor

Version: 7.0.6
 package org.springframework.web.context.request.async;
 public interface DeferredResultProcessingInterceptor {
     default <T> void beforeConcurrentHandling(NativeWebRequest request, 1
                                         DeferredResult<T> deferredResult)
                                         throws Exception;
     default <T> void preProcess(NativeWebRequest request, 2
                                 DeferredResult<T> deferredResult)
                                 throws Exception;
     default <T> void postProcess(NativeWebRequest request, 3
                                  DeferredResult<T> deferredResult,
                                  @Nullable Object concurrentResult)
                                  throws Exception;
     default <T> boolean handleTimeout(NativeWebRequest request, 4
                                       DeferredResult<T> deferredResult)
                                       throws Exception;
     default <T> boolean handleError(NativeWebRequest request, 5
                                     DeferredResult<T> deferredResult,
                                     Throwable t)
                                     throws Exception;
     default <T> void afterCompletion(NativeWebRequest request, 6
                                      DeferredResult<T> deferredResult)
                                      throws Exception;
 }
1Invoked immediately before the start of concurrent handling, in the same thread that started it.
2Invoked immediately after the start of concurrent handling, in the same thread that started it.
3Invoked after a DeferredResult has been set, via DeferredResult#setResult(Object) or DeferredResult#setErrorResult(Object), and is also ready to handle the concurrent result.
4Invoked from a container thread when an async request times out before the DeferredResult has been set.
5Invoked from a container thread when an error occurred while processing an async request before the DeferredResult has been set.
6Invoked from a container thread when an async request completed for any reason including timeout and network error.


Example


Creating the DeferredResultProcessingInterceptor

package com.logicbig.example;

import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
import java.time.LocalTime;

public class MyDeferredResultProcessingInterceptor implements
        DeferredResultProcessingInterceptor {

    @Override
    public <T> void beforeConcurrentHandling(NativeWebRequest request,
                                             DeferredResult<T> deferredResult)
            throws Exception {
        log("beforeConcurrentHandler called");
    }

    @Override
    public <T> void preProcess(NativeWebRequest request,
                               DeferredResult<T> deferredResult) throws Exception {
        log("preProcess called");
    }

    @Override
    public <T> void postProcess(NativeWebRequest request,
                                DeferredResult<T> deferredResult,
                                Object concurrentResult) throws Exception {
        log("postProcess called");
    }

    @Override
    public <T> boolean handleTimeout(NativeWebRequest request,
                                     DeferredResult<T> deferredResult) throws Exception {
        log("handleTimeout called");
        return false;
    }

    @Override
    public <T> void afterCompletion(NativeWebRequest request,
                                    DeferredResult<T> deferredResult) throws Exception {
        log("afterCompletion called");
    }

    private static void log(String msg) {
        System.out.printf("%s DeferredResultInterceptor [%s] - %s%n",
                          LocalTime.now(),
                          Thread.currentThread().getName(),
                          msg);
    }
}


Registering the interceptor

Similar to our last tutorial example, We can register the above interceptor in the HandlerInterceptor#preHandle method using WebAsyncManager:

package com.logicbig.example;

import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
import java.time.LocalTime;

public class MyDeferredResultProcessingInterceptor implements
        DeferredResultProcessingInterceptor {

    @Override
    public <T> void beforeConcurrentHandling(NativeWebRequest request,
                                             DeferredResult<T> deferredResult)
            throws Exception {
        log("beforeConcurrentHandler called");
    }

    @Override
    public <T> void preProcess(NativeWebRequest request,
                               DeferredResult<T> deferredResult) throws Exception {
        log("preProcess called");
    }

    @Override
    public <T> void postProcess(NativeWebRequest request,
                                DeferredResult<T> deferredResult,
                                Object concurrentResult) throws Exception {
        log("postProcess called");
    }

    @Override
    public <T> boolean handleTimeout(NativeWebRequest request,
                                     DeferredResult<T> deferredResult) throws Exception {
        log("handleTimeout called");
        return false;
    }

    @Override
    public <T> void afterCompletion(NativeWebRequest request,
                                    DeferredResult<T> deferredResult) throws Exception {
        log("afterCompletion called");
    }

    private static void log(String msg) {
        System.out.printf("%s DeferredResultInterceptor [%s] - %s%n",
                          LocalTime.now(),
                          Thread.currentThread().getName(),
                          msg);
    }
}

The Controller:

package com.logicbig.example;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import java.time.LocalTime;

@Controller
public class MyWebController {
    @GetMapping("/test")
    @ResponseBody
    public DeferredResult<String> handleTestRequest() {

        log("handler called");

        final DeferredResult<String> deferredResult = new DeferredResult<>();

        new Thread(() -> {
            log("deferred#async task started");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            deferredResult.setResult("test async result");
            log("deferred#async task finished");
        }).start();
        log("handler finished");
        return deferredResult;
    }

    private static void log(String msg) {
        System.out.printf("%s Controller [%s] - %s%n",
                          LocalTime.now(),
                          Thread.currentThread().getName(),
                          msg);
    }
}

Running the example

To try examples, run embedded Jetty (configured in pom.xml of example project below):

mvn jetty:run

Using curl

$ curl -s "http://localhost:8080/test
test async result

Server Output


18:11:41.495545200 Interceptor [qtp106193777-39] DispatcherType: REQUEST - preHandle called
18:11:41.511560900 Controller [qtp106193777-39] - handler called
18:11:41.511560900 Controller [qtp106193777-39] - handler finished
18:11:41.512571400 Controller [Thread-11] - deferred#async task started
18:11:41.516476 DeferredResultInterceptor [qtp106193777-39] - beforeConcurrentHandler called
18:11:41.518473100 DeferredResultInterceptor [qtp106193777-39] - preProcess called
18:11:41.519475200 Interceptor [qtp106193777-39] DispatcherType: REQUEST - afterConcurrentHandlingStarted called
18:11:41.814437900 DeferredResultInterceptor [Thread-11] - postProcess called
18:11:41.815716200 Controller [Thread-11] - deferred#async task finished
18:11:41.818727400 Interceptor [qtp106193777-40] DispatcherType: ASYNC - preHandle called
18:11:41.844731300 Interceptor [qtp106193777-40] DispatcherType: ASYNC - postHandle called
18:11:41.844731300 Interceptor [qtp106193777-40] DispatcherType: ASYNC - afterCompletion called
18:11:41.848188900 DeferredResultInterceptor [qtp106193777-40] - afterCompletion called

This output is similar to the last example. In this case, the user is responsible to create his own thread for async processing in the controller method. User would normally call DeferredResult.setResult() at the end of the processing, ` which will cause DeferredInterceptor#postProcess invocation. To understand the all interception points, here's the flow diagram:


Integration Test

package com.logicbig.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;

@SpringJUnitWebConfig(WebConfig.class)
public class ControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvcTester mockMvcTester;

    @BeforeEach
    public void setup() {
        this.mockMvcTester = MockMvcTester.from(this.wac);
    }

    @Test
    public void testController() {
        assertThat(this.mockMvcTester.get().uri("/test").exchange())
                .hasStatusOk()
                .bodyText().isEqualTo("test async result");
    }
}
mvn clean test -Dtest="ControllerTest"

Output

$ mvn clean test -Dtest="ControllerTest"
[INFO] Scanning for projects...
[INFO]
[INFO] ------< com.logicbig.example:async-deferred-process-interceptor >-------
[INFO] Building async-deferred-process-interceptor 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ async-deferred-process-interceptor ---
[INFO] Deleting D:\example-projects\spring-mvc\async-deferred-process-interceptor\target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ async-deferred-process-interceptor ---
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\async-deferred-process-interceptor\src\main\resources
[INFO]
[INFO] --- compiler:3.15.0:compile (default-compile) @ async-deferred-process-interceptor ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 5 source files with javac [debug target 25] to target\classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ async-deferred-process-interceptor ---
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\async-deferred-process-interceptor\src\test\resources
[INFO]
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ async-deferred-process-interceptor ---
[INFO] Recompiling the module because of changed dependency.
[INFO] Compiling 2 source files with javac [debug target 25] to target\test-classes
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ async-deferred-process-interceptor ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.ControllerTest
10:48:00.676240800 Interceptor [main] DispatcherType: REQUEST - preHandle called
10:48:00.687238700 Controller [main] - handler called
10:48:00.688241600 Controller [main] - handler finished
10:48:00.689238500 Controller [Thread-1] - deferred#async task started
10:48:00.692240200 DeferredResultInterceptor [main] - beforeConcurrentHandler called
10:48:00.693239400 DeferredResultInterceptor [main] - preProcess called
10:48:00.693239400 Interceptor [main] DispatcherType: REQUEST - afterConcurrentHandlingStarted called
10:48:00.990641100 DeferredResultInterceptor [Thread-1] - postProcess called
10:48:00.991654300 Controller [Thread-1] - deferred#async task finished
10:48:00.991654300 Interceptor [main] DispatcherType: ASYNC - preHandle called
10:48:01.013337900 Interceptor [main] DispatcherType: ASYNC - postHandle called
10:48:01.013337900 Interceptor [main] DispatcherType: ASYNC - afterCompletion called
10:48:01.013337900 DeferredResultInterceptor [main] - afterCompletion called
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.338 s -- in com.logicbig.example.ControllerTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.509 s
[INFO] Finished at: 2026-08-19T10:48:01+08:00
[INFO] ------------------------------------------------------------------------
INFO: Completed initialization in 1 ms

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 3.2.9.RELEASE - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 3.2.9.RELEASE
    • 3.2.10.RELEASE
    • 3.2.11.RELEASE
    • 3.2.12.RELEASE
    • 3.2.13.RELEASE
    • 3.2.14.RELEASE
    • 3.2.15.RELEASE
    • 3.2.16.RELEASE
    • 3.2.17.RELEASE
    • 3.2.18.RELEASE
    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • javax.servlet-api:4.x
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 6.2.18
    • 6.2.19
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    Versions in green have been tested.

  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • spring-test 7.0.6 (Spring TestContext Framework)
  • junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
  • hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
  • assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
  • JDK 25
  • Maven 3.9.11

Spring MVC - DeferredResultProcessingInterceptor Example Select All Download
  • async-deferred-process-interceptor
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • MyDeferredResultProcessingInterceptor.java
        • test
          • java
            • com
              • logicbig
                • example

    See Also

    Join