Standard Futures are in Java since version 5. But recently they started to lose breath. Nowadays we do have asynchronous servlets, asynchronous HTTP clients and lot of other asynchronous libraries. Good old Futures are not good enough for connecting those pieces together.
Let’s demonstrate it on a simple example. It’s just one of many possible examples, but I think it’s the most representative one. Imagine we have a simple servlet controller that just calls some backend API, processes the result and returns it. If you think about it you will notice that we do not need any thread for most of the processing time, we are just waiting for the response. On one end we do have asynchronous servlet and on the other asynchronous HTTP client. We just need to connect those two. In the following example I will be using Spring 4 features, but it’s easy to transform it to your favorite library.
@Controller @EnableAutoConfiguration public class ListenableFutureAsyncController { // Let's use Apache Async HTTP client private final AsyncRestTemplate restTemplate = new AsyncRestTemplate( new HttpComponentsAsyncClientHttpRequestFactory() ); @RequestMapping("/") @ResponseBody DeferredResult<String> home() { // Create DeferredResult with timeout 5s final DeferredResult<String> result = new DeferredResult<>(5000); // Let's call the backend ListenableFuture<ResponseEntity<String>> future = restTemplate.getForEntity("http://www.google.com", String.class); future.addCallback( new ListenableFutureCallback<ResponseEntity<String>>() { @Override public void onSuccess(ResponseEntity<String> response) { // Will be called in HttpClient thread log("Success"); result.setResult(response.getBody()); } @Override public void onFailure(Throwable t) { result.setErrorResult(t.getMessage()); } }); // Return the thread to servlet container, // the response will be processed by another thread. return result; } public static void log(Object message) { System.out.println(format("%s %s ",Thread.currentThread().getName(), message)); } // That's all you need to start the application public static void main(String[] args) throws Exception { SpringApplication.run(ListenableFutureAsyncController.class, args); } }
The method starts by creating DeferredResult. It’s a handy abstraction around asynchronous servlets. If we return DeferredResult, servlet container thread is returned to the pool and another one is later used for sending the result. To send the result, we have to either call setResult or setErrorResult method from another thread.
In the next step we call the backend. We use Spring 4 AsyncRestTemplate which is able to wrap Apache Async HTTP Client. It returns ListenableFuture and we can use callbacks to say what to do when the backend request succeeds or fails. Then it’s straightforward to return the result. Please note that the callback is called using HttpClient I/O dispatcher thread. It’s fine in our simple case but for more CPU intensive task we would have to use another thread-pool.
The callback is what’s important, that’s what makes ListenableFutures different. In fact, it’s the only difference between Spring ListenableFuture and standard Java 5 future. It’s a small difference, but without it we would not be able to implement the example above.
If we write the code like this, we need a thread only for commencing the request and for sending the response back. The rest is handled asynchronously by NIO.
You can also notice that the code is much more complicated than equivalent synchronous code. It’s more similar to NodeJS code than to what we are used to in Java. We do have those strange callbacks, different threads processing the same request and other conceptually complicated stuff. So, if you do not need to scale to thousands concurrent requests, you might want to think twice about using this approach. But if you need to scale, this is the direction to go. Next time we will re-implement the same example using Java 8 Completable futures to see if it looks better.
The full source code is available here. It uses Spring Boot so the class you have seen is really all you need.