Spring Async REST Controller with Callable

Learn to write spring boot async rest controller which supports async request processing and returning the response using Callable interface.

1. How to use Callable for Async Processing

Writing a controller and having it handle the request asynchronously is as simple as changing the return type of the controller’s handler method.

To use the Callable interface as the return value from the REST handler method, we create a new instance of Callable and return it from the handler method immediately.

Inside the Callable implementation, we perform the request processing and return the result when it is available.

@GetMapping(value = "/api-url")
public Callable<Data> handlerMethod() 
{
	return () -> {
		//perform the operation
		return data;
	};
}

2. Async Controller Example with Callable

The given controller is the simplest example that returns the Callable<String> instead of the normal string value “Hello World !!” after 5 seconds delay while the actual logic inside the controller is accomplished.

@RestController
public class HelloWorldCallableController 
{
	@GetMapping(value = "/testCallable")
	public Callable<String> echoHelloWorld() 
	{
		return () -> {
			Thread.sleep(5000);
			return "Hello World !!";
		};
	}
}

3. Test

To test the above controller, we are using MockMvc to mock the interaction with the controller. The test waits for 5 seconds and then validates the response returned from the handler method.

@WebMvcTest(HelloWorldCallableController.class)
public class HelloWorldCallableControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @Test
  public void testHelloWorldController() throws Exception {

    MvcResult mvcResult = mockMvc.perform(get("/testCallable"))
				.andExpect(request().asyncStarted())
        .andDo(MockMvcResultHandlers.log())
				.andReturn();

    mockMvc.perform(asyncDispatch(mvcResult))
        .andExpect(status().isOk())
        .andExpect(content().contentTypeCompatibleWith("text/plain"))
        .andExpect(content().string("Hello World !!"));
  }
}

Program output.

2019-02-06 12:13:19.356 DEBUG 15384 --- [main] o.s.b.f.s.DefaultListableBeanFactory     : Returning cached instance of singleton bean 'helloWorldCallableController'
2019-02-06 12:13:19.357 DEBUG 15384 --- [main] o.s.t.web.servlet.TestDispatcherServlet  : Last-Modified value for [/testCallable] is: -1
2019-02-06 12:13:19.409 DEBUG 15384 --- [main] o.s.w.c.request.async.WebAsyncManager    : Concurrent handling starting for GET [/testCallable]
2019-02-06 12:13:19.416 DEBUG 15384 --- [main] o.s.t.web.servlet.TestDispatcherServlet  : Leaving response open for concurrent processing
2019-02-06 12:13:19.417 DEBUG 15384 --- [main] o.s.b.w.s.f.OrderedRequestContextFilter  : Cleared thread-bound request context: org.springframework.mock.web.MockHttpServletRequest@3a627c80
2019-02-06 12:13:19.463 DEBUG 15384 --- [main] o.s.test.web.servlet.result              : MvcResult details:
...
2019-02-06 12:13:24.836 DEBUG 15384 --- [mvc-task-1] o.s.w.c.request.async.WebAsyncManager    : Concurrent result value [Hello World !!] - dispatching request to resume processing
2019-02-06 12:13:24.929 DEBUG 15384 --- [main] o.s.b.w.s.f.OrderedRequestContextFilter  : Bound request context to thread: org.springframework.mock.web.MockHttpServletRequest@3a627c80
2019-02-06 12:13:24.929 DEBUG 15384 --- [main] o.s.t.web.servlet.TestDispatcherServlet  : DispatcherServlet with name '' resumed processing GET request for [/testCallable]
2019-02-06 12:13:24.930 DEBUG 15384 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /testCallable
2019-02-06 12:13:24.930 DEBUG 15384 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Returning handler method [public java.util.concurrent.Callable&amp;lt;java.lang.String&amp;gt; com.howtodoinjava.springasyncexample.web.controller.HelloWorldCallableController.echoHelloWorld()]
2019-02-06 12:13:24.930 DEBUG 15384 --- [main] o.s.b.f.s.DefaultListableBeanFactory     : Returning cached instance of singleton bean 'helloWorldCallableController'
2019-02-06 12:13:24.930 DEBUG 15384 --- [main] o.s.t.web.servlet.TestDispatcherServlet  : Last-Modified value for [/testCallable] is: -1
2019-02-06 12:13:24.931 DEBUG 15384 --- [main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Found concurrent result value [Hello World !!]
2019-02-06 12:13:24.955 DEBUG 15384 --- [main] m.m.a.RequestResponseBodyMethodProcessor : Written [Hello World !!] as "text/plain" using [org.springframework.http.converter.StringHttpMessageConverter@2bffa76d]
2019-02-06 12:13:24.956 DEBUG 15384 --- [main] o.s.t.web.servlet.TestDispatcherServlet  : Null ModelAndView returned to DispatcherServlet with name '': assuming HandlerAdapter completed request handling
2019-02-06 12:13:24.956 DEBUG 15384 --- [main] o.s.t.web.servlet.TestDispatcherServlet  : Successfully completed request
  • Notice that request handling is started on a certain thread (main).
  • Another thread is doing the processing and returning the result (mvc-task-1).
  • Finally, the request is dispatched to Spring again to handle the result and shut down the application (main and Thread-2).

Let me know if you face any errors while executing this asynchronous rest service example.

Happy Learning !!

Source Code on Github

Comments

Subscribe
Notify of
guest
1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode