2

Is it possible to fail a CompletableFuture based on a condition? In other words, convert a success result into an exceptional result? Is there an API for it?

At the moment, I am doing the following, but it feels awkward and convoluted:

CompletableFuture<CustomClass> inputFuture = doWork();
CompletableFuture<CustomClass> outputFuture = new CompletableFuture<>();

inputFuture.whenComplete((result, ex) -> {
  if (ex != null) {
    outputFuture.completeExceptionally(ex);
  } else {
    Outcome outcome = verify(result);
    if (outcome.isNegativeResult()) {
      outputFuture.completeExceptionally(outcome.getException());
    } else {
      outputFuture.complete(result);
    }
  }
});

return outputFuture;

Not very expressive. I see there is thenCompose which automatically handles the exceptional condition and reduces it to:

CompletableFuture<CustomClass> inputFuture = doWork();

return inputFuture.thenCompose(result -> {
  Outcome outcome = verify(result);
  if (outcome.isNegativeResult()) {
    return CompletableFuture.failedFuture(outcome.getException());
  } else {
    return CompletableFuture.completedFuture(result);
  }
});

It's the best with which I could come up, but it still feels a bit clumsy (temporary throw-away futures are newed up, only to transport the value).

Is there a shorter, more idiomatic or more concise way of expressing this using the CompletableFuture API? How does each solution compare when it comes to performance (or is the difference mostly negligible?)

PS. I don't know the type of exception to be propagated beforehand, but let's assume it is a checked exception and it cannot be changed to an unchecked one. Wrapping in an unchecked exception isn't an option either, because it will change the direct type of the exception as seen by consumers.

knittl
  • 246,190
  • 53
  • 318
  • 364

1 Answers1

1

I have read your question again (sorry about the confusion previously) and what you do is entirely valid. As a matter of fact, I do not know a better and more idiomatic way to do it. Although, you do no need it here, thenCompose besides being able to "flatten" a CompletableFuture, is non-blocking. There are cases when calling thenCompose instead of get/join and then wrap back into a CompletableFuture, matter a lot. Such usage is present in jdk's http client and though you do not leverage this here (nor does it really matter for your example), it is a very good habit to grow, imo.

As to temporary objects, I have expressed my opinion about the fact that the performance drop from this is almost un-existent, compared to the entire operation per-se, for example here. Also think about that HashSet uses a HashMap under the hood for more than 20 years now, and people do not complain. These temporary "holder" objects are never a problem (in my practice so far)

Eugene
  • 117,005
  • 15
  • 201
  • 306