4

I am new to CompletableFuture, I will like to call a method MetadataLoginUtil::login which can throw an exception. However, the code below is not compiled although I have 'exceptionally' written. It says that I must wrap the MetadataLoginUtil::login' within try & catch.

Please advise. Thanks ahead !

public void run() throws ConnectionException {
    CompletableFuture<Void> mt = CompletableFuture.supplyAsync(MetadataLoginUtil::login)
            .exceptionally(e -> {
                System.out.println(e);
                return null;
            })
            .thenAccept(e -> System.out.println(e));
}
GameDroids
  • 5,584
  • 6
  • 40
  • 59
sfdcdev
  • 199
  • 4
  • 12

2 Answers2

18

This is not a deficiency of how CompletableFuture works in general, but of the convenience methods, all using functional interfaces not allowing checked exceptions. You can solve this with an alternative supplyAsync method:

public static <T> CompletableFuture<T> supplyAsync(Callable<T> c) {
    CompletableFuture<T> f=new CompletableFuture<>();
    CompletableFuture.runAsync(() -> {
        try { f.complete(c.call()); } catch(Throwable t) { f.completeExceptionally(t); }
    });
    return f;
}

This is basically doing the same as the original supplyAsync, but allowing checked exceptions. So you can use it right like in your original attempt, only redirecting the initial supplyAsync call.

CompletableFuture<Void> mt = supplyAsync(MetadataLoginUtil::login)
    .exceptionally(e -> { System.out.println(e); return null; } )
    .thenAccept(e -> System.out.println(e));
Holger
  • 285,553
  • 42
  • 434
  • 765
3

CompletableFuture.supplyAsync(Supplier<U>) expects a java.util.function.Supplier<U> instance, and Supplier.get() method's signature does not allow for checked exceptions. To see this clearly, notice that CompletableFuture.supplyAsync(MetadataLoginUtil::login) is equivalent to

CompletableFuture<Void> mt = CompletableFuture.supplyAsync(new Supplier<Void>() {
        @Override
        public Void get() {
            return MetadataLoginUtil.login();
        }
    })

which clearly cannot compile.

You can handle the exception inside your Supplier, changing CompletableFuture.supplyAsync(MetadataLoginUtil::login).exceptionally(e -> {System.out.println(e); return null; } ) to

CompletableFuture.supplyAsync(() -> {
        try {
            return MetadataLoginUtil.login();
        } catch (Exception e) {
            System.out.println(e);
            return null;
        }
    })

It's not pretty, but CompletableFuture's API doesn't seem to work with checked exceptions very well.

korolar
  • 1,340
  • 1
  • 11
  • 20
  • 1
    Thanks, so what is the point of 'exceptionally' ? I dont see why we need this – sfdcdev Nov 25 '16 at 07:23
  • The problem is rather, that the complete lambda principle does not cover checked exceptions at all. Still, you can use exceptionally or handle for RuntimeExceptions and its derivates. One quite common pattern is, wrapping exceptions in lambdas in runtime exceptions and rethrow: ... catch(Exception e) { throw new RuntimeException(e); } – mtj Nov 25 '16 at 08:29
  • 1
    @mtj: lambdas and exception work together; it’s only a matter of how the interfaces are declared. You could even make the exceptions type parameters of the interfaces to declare methods throwing precisely what the provided function arguments could throw. This, however, didn’t work with early compiler versions of Java 8, when the APIs were fixed. – Holger Nov 25 '16 at 08:32
  • @Holger : OK, to correct myself, it is not the lambda *principle* but the declaration of the default functional interfaces. Still, the solution as they implemented it, is far from optimal and puts much too much load on the user instead of sensibly covering the existance of checked exception in the new language feature. However, this is a point for discussion at another place and time... – mtj Nov 25 '16 at 08:37