Search code examples
javagenericscompletable-future

How to make CompletableFuture exceptonally function returns Collection type?


The project that I work on have some code that can be simplified as the following two functions:

CompletionStage<Collection<String>> fn1(String prefix) {
  return fn2()
      .thenApply(list -> list.stream()
          .map(s -> prefix + s)
          .collect(Collectors.toList())
      );
}

CompletionStage<List<String>> fn2() {
  return CompletableFuture.completedFuture(List.of("results", "of", "computation"));
}

It works well. However, fn1 cannot compile after adding an exceptionally step:

CompletionStage<Collection<String>> fn1(String prefix) {
  return fn2()
      .thenApply(list -> list.stream()
          .map(s -> prefix + s)
          .collect(Collectors.toList())
      ).exceptionally(t -> {
        // Log the error
        // ...

        // return an empty collection
        Collection<String> emptyCol = List.of();
        return emptyCol;  // Error: Collection<String> cannot be converted to List<String>

        // Also tried the following two ways
        // List<String> emptyList = List.of();
        // return emptyList;   // Error: CompletionStage<List<String>> cannot be converted to CompletionStage<Collection<String>>
        // return emptyList.stream().collect(Collectors.toList()); // Error same as above
      });
}

How to fix it, supposing that we cannot change the signature of fn1? Why .collect(Collectors.toList()) is fine for thenApply?

BTW, the JDK is 11.


Solution

  • exceptionally must return the same type as the original future. Since the generic type of your return type is Collection<String>, you must help the Java compiler figure out that your future really returns a Collection and not a List.

    All options to fix this issue are trivial: type witness, cast, or temporary variable.

    Type witness:

    CompletionStage<Collection<String>> fn1(String prefix) {
        return fn2()
                .<Collection<String>>thenApply(list -> list.stream()
                        .map(s -> prefix + s)
                        .collect(Collectors.toList())
                ).exceptionally(t -> Collections.emptyList());
    }
    

    Cast:

    CompletionStage<Collection<String>> fn1(String prefix) {
        return fn2()
                .thenApply(list -> (Collection<String>)list.stream()
                        .map(s -> prefix + s)
                        .collect(Collectors.toList())
                ).exceptionally(t -> Collections.emptyList());
    }
    

    Variable:

    CompletionStage<Collection<String>> fn1(String prefix) {
        final CompletionStage<Collection<String>> stage = fn2()
                .thenApply(list -> list.stream()
                        .map(s -> prefix + s)
                        .collect(Collectors.toList()));
        return stage.exceptionally(t -> Collections.emptyList());
    }