2

I am new to the world of CompletableFuture. I am trying to do some negative tests, in a way that will allow me to throw an exception intentionally. This exception will decide the PASS/FAIL.

Here's the code snippet:

    protected CompletableFuture<Response> executeAsync(@NonNull Supplier<Response> call) {
        return CompletableFuture
                .supplyAsync(call::get)
                .whenCompleteAsync((response, exception) -> {
                    if (exception == null) {
                        try {
                            Helper.throwIfNotExpected(clientProperties.getName(), response, null);
                        } catch (ServiceException e) {
                            throw new ServiceException(null,e.getMessage(),null);
                        }
                    } else {
                        log.error("Async API call failed.", exception);
                    }
                });
    }

This gives me an error saying unhandled exception in catch part. I looked up examples and documentation but could not find much information about Exception handling in supplyAsync/whenCompleteAsync. Thanks in advance.

sagar vasekar
  • 55
  • 1
  • 1
  • 8
  • Have you tried throwing an unchecked exception? – dan1st Mar 29 '22 at 22:17
  • Have you tried catching the checked exceptions that Helper.throwIfNotExpected is declared as throwing? If it's "throws Exception" then you need to "catch Exception" (or Throwable, but I'd suggest not going wider than you need). – passer-by Mar 29 '22 at 22:19
  • Helper.throwIfNotExpected() "throws ServiceException". – sagar vasekar Mar 29 '22 at 22:55
  • Throwing unchecked exception gives me same error. – sagar vasekar Mar 29 '22 at 22:56
  • 2
    Side note: when you have a `Supplier` and call a method expecting a `Supplier`, just pass the `Supplier`, i.e. use `supplyAsync(call)` instead of `supplyAsync(call::get)` – Holger Mar 30 '22 at 15:58

1 Answers1

2

A good way to work around the shortcoming of CompletableFuture regarding checked exceptions is to delegate to another CompletableFuture instance. For your example it would look like something along the lines of

protected CompletableFuture<Response> executeAsync(Supplier<Response> call) {
    CompletableFuture<Response> delegate = new CompletableFuture<>();

    CompletableFuture
        .supplyAsync(call)
        .whenCompleteAsync((response, exception) -> {
            if (exception == null) {
                try {
                    Helper.throwIfNotExpected(clientProperties.getName(), response, null);
                    delegate.complete(response);
                } catch (ServiceException e) {
                    delegate.completeExceptionally(new ServiceException(null,e.getMessage(),null));
                }
            } else {
                log.error("Async API call failed.", exception);
                delegate.completeExceptionally(exception);
            }
        });

    return delegate;
}
michid
  • 10,536
  • 3
  • 32
  • 59