One day I was rewriting poorly implemented multi-threaded code that was blocking at some point on Future.get() : public void serve() throws InterruptedException, ExecutionException, TimeoutException { final Future<Response> responseFuture = asyncCode(); final Response response = responseFuture.get(1, SECONDS); send(response); } private void send(Response response) { //... } This was actually an Akka application written in Java with a thread pool of 1000 threads (sic!) - all of them blocked on this get() call. Otherwise system couldn't keep up with the number of concurrent requests. After refactoring we got rid of all these threads and introduced just one, significantly reducing memory footprint. Let's simplify a bit and show examples in Java 8. The first step is to introduce CompletableFuture instead of plain Future (see: tip 9 ). It's simple if: you control how tasks are submitted to ExecutorService : just use CompletableFuture.supplyAsync(..