public static <T, E extends Throwable> CompletableFuture<T> handleException(
            CompletableFuture<? extends T> completableFuture,
            Class<E> exceptionClass,
            Function<? super E, ? extends T> exceptionHandler) {
        final CompletableFuture<T> handledFuture = new CompletableFuture<>();
        checkNotNull(completableFuture)
                .whenComplete(
                        (result, throwable) -> {
                            if (throwable == null) {
                                handledFuture.complete(result);
                            } else if (exceptionClass.isAssignableFrom(throwable.getClass())) {
                                final E exception = exceptionClass.cast(throwable);
                                try {
                                    handledFuture.complete(exceptionHandler.apply(exception));
                                } catch (Throwable t) {
                                    handledFuture.completeExceptionally(t);
                                }
                            } else {
                                handledFuture.completeExceptionally(throwable);
                            }
                        });
        return handledFuture;
    }
@Test
    void testHandleExceptionWithNotMatchingExceptionallyCompletedFuture() {
        final CompletableFuture<String> future = new CompletableFuture<>();
        final CompletableFuture<String> handled =
                FutureUtils.handleException(
                        future, UnsupportedOperationException.class, exception -> "handled");
        final IllegalArgumentException futureException = new IllegalArgumentException("foobar");
        future.completeExceptionally(futureException);

        assertThatFuture(handled)
                .eventuallyFailsWith(ExecutionException.class)
                .withCause(futureException);
    }