Ограничения Future: результат забирают блокирующим get(). Чтобы объединить два асинхронных вызова, нужно заблокировать поток на каждом. Колбэков нет, комбинировать нельзя.
CompletableFuture реализует Future и CompletionStage:
CompletableFuture<User> user = supplyAsync(() -> loadUser(id), io);
CompletableFuture<List<Order>> orders = supplyAsync(() -> loadOrders(id), io);
user.thenCombine(orders, Profile::new)
.thenApply(this::render)
.exceptionally(e -> fallbackPage())
.thenAccept(this::send);
thenApply — преобразовать значение;thenCompose — следующий шаг сам асинхронный (развернуть вложенный);thenCombine — объединить два независимых результата;allOf, anyOf — ждать группу.
Ошибки. Исключение в стадии не летит сразу: future завершается с ошибкой, следующие then* пропускаются, пока не встретится exceptionally, handle или whenComplete. При join() исключение приходит в CompletionException, при get() — в ExecutionException; настоящая причина — в getCause().
Потоки. Продолжение без суффикса Async может выполниться в потоке, завершившем предыдущую стадию, или в вызывающем. Async-варианты без аргумента используют ForkJoinPool.commonPool() — блокирующие операции отдают в свой executor.