javamultithreadingasynchronouscompletable-futurecompletion-stage

Difference between completeExceptionally and obtrudeException


Just going through the CompletableFuture documentation and stumbled upon the completeExceptionally and obtrudeException methods and is having a hard time comprehending the difference and use case. Can the community help understand the difference and the use case with an example?


Solution

  • Explanation

    The difference is subtle but important. From the official documentation:

    If not already completed, causes invocations of get() and related methods to throw the given exception.

    Forcibly causes subsequent invocations of method get() and related methods to throw the given exception, whether or not already completed. [...]

    So they differ in their behavior regarding CompletableFutures that are already completed.

    Basically, a future can either be completed or still pending (not completed). When you call completeExceptionally or obtrudeException, the behavior differs depending on the state of the future at that point in time.


    Already completed future

    Consider this example where the future is already completed at the moment of calling the method:

    CompletableFuture<String> future = CompletableFuture.completedFuture("hello world");
    
    future.completeExceptionally(new RuntimeException("Oh noes!"));
    System.out.println(future.get()); // Prints "hello world" just fine
    

    versus

    CompletableFuture<String> future = CompletableFuture.completedFuture("hello world");
    
    future.obtrudeException(new RuntimeException("Oh noes!"));
    System.out.println(future.get()); // Throws the exception
    

    Not completed future

    And in case the future is not completed yet, they will both throw an exception:

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { 
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
        return "hello world";
    });
    
    future.completeExceptionally(new RuntimeException("Oh noes!"));
    System.out.println(future.get());
    

    and

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { 
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
        return "hello world";
    });
    
    future.obtrudeException(new RuntimeException("Oh noes!"));
    System.out.println(future.get());
    

    complete and obtrudeValue

    Likewise there are also the methods complete and obtrudeValue which behave in the same way, but instead of throwing an exception, you can supply a value instead.

    So complete basically completes the future with the given value, in case the future is not done yet, otherwise it does not do anything.

    While obtrudeValue will supply the given value regardless, so it resets or cancels whatever the future already computed and replaces it by the given value instead.