How does CompletableFuture handle exceptions, and how do exceptionally, handle, and whenComplete compare?
exceptionally(Function<Throwable, T>) only runs if the upstream stage completed with an exception, and it supplies a recovery value to use instead -- it behaves similarly to a catch block, and is skipped entirely on the success path. handle(BiFunction<T, Throwable, U>) always runs regardless of whether the upstream stage succeeded or failed, receiving both the result and the exception (one of which will be null), and it must return a new value, which lets it both recover from failure and transform a successful result in one step -- closer to a combined try-catch-transform. whenComplete(BiConsumer<T, Throwable>) also always runs and receives both the result and exception, but it can only observe them for side effects like logging or metrics and cannot change the outcome that flows to the next stage -- any unhandled exception is still propagated onward exactly as if whenComplete weren't there, much like a finally block. As a rule of thumb, use exceptionally to supply a fallback value, handle when you need to branch cleanly on success versus failure, and whenComplete purely for side effects that shouldn't alter the result.
Ready to master this question?
Generate a complete walkthrough — background, the full answer in plain language, a working code example explained line by line, a real-world scenario, common mistakes, and how this same question gets asked in different ways.
Sign in to generate a response