Completable Future Failure
A `CompletableFuture` can represent either a successful result or an exceptional completion. Java provides methods such as `exceptionally`, `handle`, and `whenComplete` to observe or recover from failures without blocking the calling thread.
A `CompletableFuture<T>` completes normally with a value of type `T` or completes exceptionally with a `Throwable`. An exception thrown inside a future's asynchronous function is captured by the future rather than immediately thrown on the thread that submitted the task.
Failure handling is part of the future pipeline. `exceptionally` can turn a failure into a fallback value, while `handle` receives either the successful result or the exception and can produce a replacement result. `whenComplete` is primarily for observation or side effects and preserves the original success or failure.
Failures also propagate through dependent stages. If a stage completes exceptionally and no recovery occurs, a dependent stage generally remains exceptional. This means a pipeline can carry an error forward until a recovery or terminal observation handles it.
Calling `join()` on an exceptionally completed future throws a `CompletionException` whose cause represents the underlying failure. `get()` also reports failure, but because it follows the `Future` API it uses checked exceptions such as `ExecutionException`.
Key points
- Exceptions thrown inside a future stage complete that future exceptionally.
- `exceptionally` can recover by supplying a fallback value.
- `handle` receives both the result and possible exception and can transform either.
- `whenComplete` observes completion but does not normally recover from failure.
- `join()` exposes an exceptional completion as a `CompletionException`.
Interview questions
What happens when a `CompletableFuture` stage throws an exception?
The exception causes that future stage to complete exceptionally. It is represented by the future rather than being automatically thrown to the thread that created the pipeline. Dependent stages can observe or recover from the failure. If the failure is not handled, it can propagate through later stages.
When would you use `exceptionally`?
Use `exceptionally` when a failed computation should be converted into a fallback value. The function receives the failure and returns a value compatible with the future's result type. A successful upstream result does not invoke this recovery function. It is useful when the recovery logic only needs to deal with failure.
What is the difference between `handle` and `whenComplete`?
Both receive information about completion, including a possible exception. `handle` is a transformation stage, so its return value determines the result of the next future. `whenComplete` is primarily for side effects such as logging or metrics and normally preserves the original outcome. Therefore, recovery logic generally belongs in `handle` rather than `whenComplete`.
What exception do you see when calling `join()` on a failed `CompletableFuture`?
`join()` throws a `CompletionException` when the future completed exceptionally. The original failure is available as the cause of that exception. Unlike `get()`, `join()` does not require callers to handle checked exceptions. This makes `join()` convenient when the surrounding code already uses unchecked exception handling.