Enabling native asynchronous support
Spring Async mechanism is built on native servlet asynchronous specification. That means 'asyncSupported' must be set to true on the Servlet level i.e. DispatcherServlet.
If you are using JavaConfig along with @EnableWebMvc or Spring boot with @SpringBootApplication then this flag is set to true by default. We have not been enabling it explicitly in our previous examples.
If you are using some other way of configuration and things don't work for you please check out spring ref here.
Using a custom TaskExecutor
In cases where Spring manages threads instead of the developer creating them manually—such as when a handler method returns a Callable or StreamingResponseBody— Spring uses SimpleAsyncTaskExecutor by default.
Following code show how to use other implementation of TaskExecutor:
package com.logicbig.example;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@ComponentScan
@Configuration
public class AsyncConfig implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
ThreadPoolTaskExecutor t = new ThreadPoolTaskExecutor();
t.setCorePoolSize(10);
t.setMaxPoolSize(100);
t.setQueueCapacity(50);
t.setAllowCoreThreadTimeOut(true);
t.setKeepAliveSeconds(120);
t.setThreadNamePrefix("my-thread-");
t.initialize();
configurer.setTaskExecutor(t);
}
}
Handler returning Callable
package com.logicbig.example;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.concurrent.Callable;
@Controller
public class MyWebController {
@GetMapping("/test")
@ResponseBody
public Callable<String> handleRequest(HttpServletRequest r) {
System.out.println("asyncSupported: " + r.isAsyncSupported());
System.out.println(Thread.currentThread().getName());
return new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return "some string";
}
};
}
}
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" some string
Server Output asyncSupported: true qtp1106933404-18 my-thread-2
Handler returning StreamingResponseBody
package com.logicbig.example;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import java.io.IOException;
import java.io.OutputStream;
@Controller
public class MyWebController2 {
@GetMapping("/test2")
public StreamingResponseBody handleRequest(HttpServletRequest r) {
System.out.println("asyncSupported: " + r.isAsyncSupported());
System.out.println(Thread.currentThread().getName());
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
System.out.println(Thread.currentThread().getName());
outputStream.write("from test request".getBytes());
}
};
}
}
$ curl -s "http://localhost:8080/test2" from test request
Server Output asyncSupported: true qtp1106933404-20 my-thread-3
Wrapping Callable in WebAsyncTask
In cases where we return Callable implementation from the handler methods, We can wrap it in a WebAsyncTask which allows to set timeout and a custom task executor per callable basis:
package com.logicbig.example;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
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.WebAsyncTask;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
@Controller
public class MyWebController3 {
@GetMapping("/test3")
@ResponseBody
public WebAsyncTask<String> handleRequest(HttpServletRequest r) {
System.out.println("asyncSupported: " + r.isAsyncSupported());
System.out.println(Thread.currentThread().getName());
Callable<String> callable = () -> {
System.out.println(Thread.currentThread().getName());
return "WebAsyncTask test";
};
ConcurrentTaskExecutor t = new ConcurrentTaskExecutor(
Executors.newFixedThreadPool(1));
return new WebAsyncTask<>(10000L, t, callable);
}
}
$ curl -s "http://localhost:8080/test3" WebAsyncTask test
Server Output asyncSupported: true qtp1106933404-18 pool-6-thread-1
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 4.2.0.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)
- JDK 25
- Maven 3.9.11
|