Coder Coder - 3 months ago 18
Java Question

Returning List of completable future with Optional object

I'm trying to return a list of completable future with something like as shown below,

List<CompletableFuture<Optional<ModelObject>>> collect =
listOfModel.
stream().
map(modelObject -> CompletableFuture.supplyAsync(
()-> {
try {
return callMethodWithReturnTypeOfOptional<ModelObject>(modelObject);
} catch(Exception e) {
LOGGER.error("Something bad happened");
return null; // what should be returned here?
// return Optional.<ModelObject>empty();
}
}
, executor)).
collect(Collectors.<CompletableFuture<Optional<ModelObject>>>toList());


Is there a better way to handling exception here?
Returning Optional.empty(); is a good practice?
Thanks for help!

Answer

It’s not clear why callMethodWithReturnTypeOfOptional returns an Optional and throws exceptions, but if we assume that returning an empty optional and throwing an exception have different meanings, we should not discard that information. So the answer is, you should not catch the exception at all, so that the CompletableFuture can report it. If the method in question declares checked exceptions, just catch, wrap and rethrow them:

List<CompletableFuture<Optional<ModelObject>>> collect = 
    listOfModel.stream()
    .map(modelObject -> CompletableFuture.supplyAsync(
        ()-> {
            try {
                return callMethodWithReturnTypeOfOptional(modelObject);
            } catch(Exception e) {
                throw new CompletionException(e);
            }
        },
        executor))
    .collect(Collectors.toList());

Using CompletionException as wrapper exception has the advantage that the CompletableFuture will not wrap the exception in another CompletionException, so this exception type is a natural choice within an action passed to a CompletableFuture.

Note that you can avoid capturing lambda expression in a natural way here:

List<CompletableFuture<Optional<ModelObject>>> collect = 
    listOfModel.stream()
    .map(CompletableFuture::completedFuture)
    .map(f -> f.thenApplyAsync(
        modelObject -> {
            try {
                return callMethodWithReturnTypeOfOptional(modelObject);
            } catch(Exception e) {
                throw new CompletionException(e);
            }
        },
        executor))
    .collect(Collectors.toList());

If the method does not throw checked exceptions, this code simplifies to

List<CompletableFuture<Optional<ModelObject>>> collect = 
    listOfModel.stream()
    .map(CompletableFuture::completedFuture)
    .map(f -> f.thenApplyAsync(Owner::callMethodWithReturnTypeOfOptional, executor))
    .collect(Collectors.toList());