Olayinka S. Folorunso

Pissed cause your hustles ain't worth a shit
— Obie Trice, Cry Now (Shady Remix)

Chained CompletableFutures - Java 8

Berlin, Germany - Wednesday, Jun 22 2016

Extract from the dumb question I asked on StackOverflow:

So I have a method that returns a CompletableFuture. Before returning, this method adds a block with thenAccept which is executed after the CompletableFuture completes.

The caller of this method also adds another block with thenAccept. Obviously this can go on with multiple chained calls.

In what order are the CompletionStage returned by the thenAccept invocations executed? Is it guaranteed to be the order in which they are added? If not, how can one guarantee that they are executed in the order in which they are added?

I was dumb enough to believe that blocks are executed in order. Might be I am not too dumb, I was obviously not well convinced and that led me to asking on StackOverflow

The only answer to the question suggested to use CompletableFuture#thenAcceptBoth which takes a CompletionStage and a BiConsumer as arguments. This is assuming that I need a result from the CompletionStages returned from the chained thenAccept because the BiConsumer consumes the result of the CompletableFuture and the CompletionStage. My use case doesn’t need the CompletionStages. Different components of the system need a Future.

Here is what I was doing before asking the question looks like

@Component
public static class BaseComponent {


    CompletableFuture<Integer> longTask() throws InterruptedException {
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 0b0000;
        });
    }

}

@Component
public static class FirstComponent {

    @Autowired
    BaseComponent baseComponent;

    CompletableFuture<Integer> doFirstSomething() throws InterruptedException {
        CompletableFuture<Integer> future = baseComponent.longTask();
        future.thenAccept(this::accept);
        return future;
    }

    void accept(int i) {
    }

}

@Component
public static class SecondComponent {

    @Autowired
    FirstComponent firstComponent;

    CompletableFuture<Integer> doSecondSomething() throws InterruptedException {
        CompletableFuture<Integer> future = firstComponent.doFirstSomething();
        future.thenAccept(this::accept);
        return future;
    }

    void accept(int i) {
    }

}

The thenAccept methods from the two components are not guaranteed to be executed in the desired order. What then do I do if I want to guarantee the order of execution? Thanks to Achim who suggested this to me because I was too lazy to read the documentation, I ended up using thenApply

@Component
public static class BaseComponent {


    CompletableFuture<Integer> longTask() throws InterruptedException {
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 0b0000;
        });
    }

}

@Component
public static class FirstComponent {

    @Autowired
    BaseComponent baseComponent;

    CompletableFuture<Integer> doFirstSomething() throws InterruptedException {
        return baseComponent.longTask().thenApply(this::apply);
    }

    int apply(int i) {
        //do anything
        return i;
    }

}

@Component
public static class SecondComponent {

    @Autowired
    FirstComponent firstComponent;


    CompletableFuture<Integer> doSecondSomething() throws InterruptedException {
        return firstComponent.doFirstSomething().thenApply(this::apply);
    }

    int apply(int i) {
        //do anything
        return i;
    }

}

What is the difference between the two? The first approach consumes the value from the Future on the same thread of the caller and not that of the Future and thus we can’t guarantee order of execution. When the Future completes the consumers are triggered and invoked by their own thread.

The second approach on the other hand ties the consumption to the Future returned to the caller which enforces the consumption before the Future returned to the caller terminates and invokes the next consumer. In fact, the Future returned to the second component involves the consumption from the first component.

The added bonus of the second approach is that we can return something else from the first (or second) component and not necessarily the template type of the Future returned from the base component. Something like this

@Component
public static class BaseComponent {


    CompletableFuture<Integer> longTask() throws InterruptedException {
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 0b0000;
        });
    }

}

@Component
public static class FirstComponent {

    @Autowired
    BaseComponent baseComponent;

    CompletableFuture<String> doFirstSomething() throws InterruptedException {
        return baseComponent.longTask().thenApply(this::apply);
    }

    String apply(Integer i) {
        //do anything
        return i.toString();
    }

}

@Component
public static class SecondComponent {

    @Autowired
    FirstComponent firstComponent;


    CompletableFuture<Character> doSecondSomething() throws InterruptedException {
        return firstComponent.doFirstSomething().thenApply(this::apply);
    }

    Character apply(String i) {
        //do anything
        return i.charAt(0);
    }

}

With that, I am going to sleep.