Enterprise Java

Understanding Callable and Spring DeferredResult

1. Introduction

Asynchronous support introduced in Servlet 3.0 offers the possibility to process an HTTP request in another thread. This is specially interesting when you have a long running task, since while another thread processes this request, the container thread is freed and can continue serving other requests.

This topic has been explained many times, but there seems to be a little bit of confusion regarding those classes provided by the Spring framework which take advantage of this functionality. I am talking about returning Callable and DeferredResult from a @Controller.

In this post I will implement both examples in order to show its differences.

All the examples shown here consist on implementing a controller which will execute a long running task, and then return the result to the client. The long running task is processed by the TaskService:

@Service
public class TaskServiceImpl implements TaskService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Override
    public String execute() {
        try {
            Thread.sleep(5000);
            logger.info("Slow task executed");
            return "Task finished";
        } catch (InterruptedException e) {
            throw new RuntimeException();
        }
    }
}

The web application is built with Spring Boot. We will be executing the following class to run our examples:

@SpringBootApplication
public class MainApp {
    
    public static void main(String[] args) {
        SpringApplication.run(MainApp.class, args);
    }
}

The source code with all these examples can be found at the Github Spring-Rest repository.

2. Starting with a blocking controller

In this example, a request arrives to the controller. The servlet thread won’t be released until the long running method is executed and we exit the @RequestMapping annotated method.

@RestController
public class BlockingController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final TaskService taskService;
    
    @Autowired
    public BlockingController(TaskService taskService) {
        this.taskService = taskService;
    }
    
    @RequestMapping(value = "/block", method = RequestMethod.GET, produces = "text/html")
    public String executeSlowTask() {
        logger.info("Request received");
        String result = taskService.execute();
        logger.info("Servlet thread released");
        
        return result;
    }
}

If we run this example at http://localhost:8080/block, looking at the logs, we can see that the servlet request is not released until the long running task has been processed (5 seconds later):

2015-07-12 12:41:11.849  [nio-8080-exec-6] x.s.web.controller.BlockingController    : Request received
2015-07-12 12:41:16.851  [nio-8080-exec-6] x.spring.web.service.TaskServiceImpl     : Slow task executed
2015-07-12 12:41:16.851  [nio-8080-exec-6] x.s.web.controller.BlockingController    : Servlet thread released

3. Returning Callable

In this example, instead of returning directly the result, we will return a Callable:

@RestController
public class AsyncCallableController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final TaskService taskService;
    
    @Autowired
    public AsyncCallableController(TaskService taskService) {
        this.taskService = taskService;
    }
    
    @RequestMapping(value = "/callable", method = RequestMethod.GET, produces = "text/html")
    public Callable<String> executeSlowTask() {
        logger.info("Request received");
        Callable<String> callable = taskService::execute;
        logger.info("Servlet thread released");
        
        return callable;
    }
}

Returning Callable implies that Spring MVC will invoke the task defined in the Callable in a different thread. Spring will manage this thread by using a TaskExecutor. Before waiting for the long task to finish, the servlet thread will be released.

Let’s take a look at the logs:

2015-07-12 13:07:07.012  [nio-8080-exec-5] x.s.w.c.AsyncCallableController          : Request received
2015-07-12 13:07:07.013  [nio-8080-exec-5] x.s.w.c.AsyncCallableController          : Servlet thread released
2015-07-12 13:07:12.014  [      MvcAsync2] x.spring.web.service.TaskServiceImpl     : Slow task executed

You can see that we have returned from the servlet before the long running task has finished executing. This doesn’t mean the client has received a response. The communication with the client is still open waiting for the result, but the thread that received the request has been released and can serve another client’s request.

4. Returning DeferredResult

First, we need to create a DeferredResult object. This object will be returned by the controller. What we will accomplish is the same with Callable, to release the servlet thread while we process the long running task in another thread.

@RestController
public class AsyncDeferredController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final TaskService taskService;
    
    @Autowired
    public AsyncDeferredController(TaskService taskService) {
        this.taskService = taskService;
    }
    
    @RequestMapping(value = "/deferred", method = RequestMethod.GET, produces = "text/html")
    public DeferredResult<String> executeSlowTask() {
        logger.info("Request received");
        DeferredResult<String> deferredResult = new DeferredResult<>();
        CompletableFuture.supplyAsync(taskService::execute)
            .whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));
        logger.info("Servlet thread released");
        
        return deferredResult;
    }

So, what’s the difference from Callable? The difference is this time the thread is managed by us. It is our responsibility to set the result of the DeferredResult in a different thread.

What we have done in this example, is to create an asynchronous task with CompletableFuture. This will create a new thread where our long running task will be executed. Is in this thread where we will set the result.

From which pool are we retrieving this new thread? By default, the supplyAsync method in CompletableFuture will run the task in the ForkJoin pool. If you want to use a different thread pool, you can pass an executor to the supplyAsync method:

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

If we run this example, we will get the same result as with Callable:

2015-07-12 13:28:08.433  [io-8080-exec-10] x.s.w.c.AsyncDeferredController          : Request received
2015-07-12 13:28:08.475  [io-8080-exec-10] x.s.w.c.AsyncDeferredController          : Servlet thread released
2015-07-12 13:28:13.469  [onPool-worker-1] x.spring.web.service.TaskServiceImpl     : Slow task executed 

5. Conclusion

At a high level view, Callable and DeferredResult do the same exact thing, which is releasing the container thread and processing the long running task asynchronously in another thread. The difference is in who manages the thread executing the task.

I’m publishing my new posts on Google plus and Twitter. Follow me if you want to be updated with new content.

Xavier Padro

Xavier is a software developer working in a consulting firm based in Barcelona. He is specialized in web application development with experience in both frontend and backend. He is interested in everything related to Java and the Spring framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Kumar
Kumar
6 years ago

how do i pass arguments with execute method (couldnt call with :: )

Arpan Jain
Arpan Jain
4 years ago
Reply to  Kumar

Instead of this:
CompletableFuture.supplyAsync(taskService::execute)
.whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));

Use this:
CompletableFuture.supplyAsync(() ->{return taskService.execute();})
.whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));

Just use this- () ->{return taskService.execute();}

Oleksandr
Oleksandr
6 years ago

Thanks Xavier!
The best explanation I saw on this topic
May be you know how Spring WebFlux implementation for this scenario will look like?

Arpan Jain
Arpan Jain
4 years ago

What do you mean by this – “The communication with the client is still open waiting for the result, but the thread that received the request has been released and can serve another client’s request.” What is the benefit of releasing the thread with receive the request? After all the connection is open. I think in the end it doesn’t matter a thread is released it is just delegating its job to another thread. But the API response will still be stuck till another thread will return the result. It’s just like opening another thread and waiting for it to… Read more »

Wilson
Wilson
4 years ago
Reply to  Arpan Jain

Yes, this make no sense to me if it’s used to just release the controller thread to process another incoming request. The thing is that, to me, i want to response the result to the client and close the communication while another thread is in processing the task, for the result of this thread’s task doesn’t require to response to client.

Is there anyway we could achieve it?

Back to top button