Search code examples
javacompletable-future

Ways to cancel downstream tasks in CompletableFuture chain when exception fired?


My database methods return a CompletableFuture to do the work with database asynchronously and then process the result on the main thread.

public interface IAccountDAO {
    CompletableFuture<ObjectId> create(AccountEntity accountEntity);
    CompletableFuture<Optional<AccountEntity>> get(String nickname);
    CompletableFuture<Void> update(AccountEntity accountEntity);
    CompletableFuture<Void> delete(AccountEntity accountEntity);
}

Each of the methods have following code inside (example of get(String) method):

@Override
public CompletableFuture<Optional<AccountEntity>> get(String nickname) {
    return CompletableFuture.supplyAsync(() -> {
        try {
            // something logic
            return Optional.of(...);
        } catch (SomethingException ex) {
           ex.printStackTrace();
           throw new CompletionException(ex);
        }
    });
}

Handling the result:

CompletableFuture<Optional<AccountEntity>> cf = get("Test_Nickname");

// Notify end user about exception during process
cf.exceptionally(ex -> {
    System.out.println("Database operation failed. Stacktrace:");
    ex.printStackTrace();
    return Optional.ofEmpty(); // I must return something fallback value that passes to downstream tasks.
});

// Downstream tasks that I would to cancel if exception fired
cf.thenAccept(...);
cf.thenRun(...);
cf.thenRun(...);

So, operation with database can fire exception. In this case I would to pass exception and using .exceptionally(...) or something like this notify the user called that method and STOP chain executing (cancel downstream tasks).

My question is: How I can cancel downstream tasks when CompletableFuture completed with exception?


Solution

  • I don't think you can cancel the downstream tasks.

    All you can do is just not execute the downstream tasks incase of exception.

    CompletableFuture<Optional<AccountEntity>> cf = get("Test_Nickname");
    
    cf.whenComplete((response, exception) -> {
        if (exception == null) {
           // Downstream tasks that I would to like to run on this response
        } else {
           //Notify end user of the exception 
        }
    });
    

    To avoid nesting.

    
    private Response transformResponse(GetResponse r) {
       try {
              return SuccessResponse();
        } catch (Exception e) {
              return FailedResponse();
        }
    }
    
    get("Test_Nickname")
    .thenApply(r -> transformResponse(r))
    .thenCompose(... //update)