Spring supports Servlet 3 based asynchronous request processing
We don't have to use Servlet 3 asynchronous API, instead Spring MVC abstracts away the thread management details from the controllers.
To use this support, the return value of a handler method has to be java.util.concurrent.Callable.
Spring MVC invokes the returned instance of Callable in a separate thread with the help of an underlying org.springframework.core.task.TaskExecutor
The request is dispatched back to the Servlet container to resume processing using the value returned by the Callable.
This example shows how to use this feature of Spring MVC
Creating 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 java.time.LocalTime;
import java.util.concurrent.Callable;
@Controller
public class MyController {
@GetMapping("test")
public @ResponseBody Callable<String> handleTestRequest() {
log("handler started");
Callable<String> callable = () -> {
log("async task started");
Thread.sleep(2000);
log("async task finished");
return "async result from thread: " +
Thread.currentThread().getName();
};
log("handler finished");
return callable;
}
private static void log(String msg) {
System.out.println(
LocalTime.now() +
" MyController [" +
Thread.currentThread().getName() + "] "
+ msg);
}
}
Running the example
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
You will see logs similar to these on the server side:
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.test.web.servlet.assertj.MvcTestResult;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@SpringJUnitWebConfig(MyWebConfig.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() {
MvcTestResult result = mockMvcTester.get()
.uri("/test")
.asyncExchange();
assertThat(result).request().hasAsyncStarted(true);
assertThat((String) result.getMvcResult().getAsyncResult())
.startsWith("async result");
}
}
mvn clean test -Dtest="ControllerTest" Output$ mvn clean test -Dtest="ControllerTest" [INFO] Scanning for projects... [WARNING] [WARNING] Some problems were encountered while building the effective model for com.logicbig.example:spring-async-processing:war:1.0-SNAPSHOT [WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-war-plugin is missing. @ line 43, column 21 [WARNING] [WARNING] It is highly recommended to fix these problems because they threaten the stability of your build. [WARNING] [WARNING] For this reason, future Maven versions might no longer support building such malformed projects. [WARNING] [INFO] [INFO] ------------< com.logicbig.example:spring-async-processing >------------ [INFO] Building spring-async-processing 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ war ]--------------------------------- [INFO] [INFO] --- clean:3.2.0:clean (default-clean) @ spring-async-processing --- [INFO] Deleting D:\example-projects\spring-mvc\spring-async-processing\target [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ spring-async-processing --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-async-processing\src\main\resources [INFO] [INFO] --- compiler:3.3:compile (default-compile) @ spring-async-processing --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 3 source files to D:\example-projects\spring-mvc\spring-async-processing\target\classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-async-processing --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-async-processing\src\test\resources [INFO] [INFO] --- compiler:3.3:testCompile (default-testCompile) @ spring-async-processing --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 1 source file to D:\example-projects\spring-mvc\spring-async-processing\target\test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ spring-async-processing --- [INFO] Using auto detected provider org.apache.maven.surefire.junit4.JUnit4Provider [WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.logicbig.example.ControllerTest 20:50:53.714 MyController [main] handler started 20:50:53.715 MyController [main] handler finished 20:50:53.721 MyController [MvcAsync1] async task started 20:50:55.732 MyController [MvcAsync1] async task finished [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.216 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: 10.771 s [INFO] Finished at: 2026-08-15T20:50:56+08:00 [INFO] ------------------------------------------------------------------------
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 3.2.9.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: 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
|