How to Use Java CompletableFuture

Learn how to use CompletableFuture for complex thread processing in Java.

CompletableFuture

In Java 8, CompletableFuture was introduced, making it possible to perform more complex Thread processing.

With CompletableFuture, you can process a result after obtaining it. You can also wait for multiple CompletableFutures to complete and then perform processing, or process while waiting until one of the CompletableFutures completes.

Return a Value as the Result of Processing and Use It for Another Process

CompletableFuture

Let’s combine a Supplier, which returns a value, and a Consumer, which receives and processes a value.

package com.devkuma.basic.completablefuture;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class SupplyAndConsume {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Supplier<Integer> initValueSupplier = () -> 100;
        Consumer<Integer> valueConsumer = value -> System.out.println(value);

        CompletableFuture<Void> future = CompletableFuture.supplyAsync(initValueSupplier)
                                                          .thenAcceptAsync(valueConsumer);

        future.get();  // Get result
    }
}

Result:

100
  • CompletableFuture.supplyAsync(Supplier)
    • Processes the Supplier asynchronously and returns a CompletableFuture instance.
  • CompletableFuture.thenAcceptAsync(Consumer)
    • When processing of the CompletableFuture instance finishes, passes the return value and executes the Consumer processing.

Return a Value as the Result of Processing, Transform and Return It, and Process That Return Value

CompletableFuture

The value is returned by a Supplier, transformed by a Function, and then received and processed by a Consumer.

package com.devkuma.basic.completablefuture;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class SupplyAndExecuteAndConsume {
    public static void main(String[] args) throws InterruptedException, ExecutionException {

        Supplier<Integer> initValueSupplier = () -> 100;
        Function<Integer, Integer> multiply = value -> value * 2;
        Consumer<Integer> valueConsumer = value -> System.out.println(value);

        CompletableFuture<Void> future = CompletableFuture.supplyAsync(initValueSupplier)
                                                          .thenApplyAsync(multiply)
                                                          .thenAcceptAsync(valueConsumer);
        future.get();
    }
}

Result:

200
  • Basically, it is the same as the previous pattern.
  • CompletableFuture.thenApplyAsync(Function)
    • Passes the result value of the CompletableFuture and executes the Function processing.

Execute One Process on Multiple Threads and Use the First Result for Another Process

CompletableFuture

The result processed by Supplier is returned, and the value received by Consumer is used for processing.

package com.devkuma.basic.completablefuture;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class RaceAndConsume {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Supplier<Integer> initValueSupplier = () -> 100;
        Supplier<Integer> anotherValueSupplier = () -> 200;
        Consumer<Integer> valueConsumer = value -> System.out.println(value);

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(initValueSupplier);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(anotherValueSupplier);

        future1.acceptEitherAsync(future2, valueConsumer).get();
    }
}

Result:

100 or 200
  • CompletableFuture.acceptEitherAsync(CompletableFuture, Consumer)
    • Uses whichever result comes first and performs Consumer processing.

Execute One Process on Multiple Threads, Use the First Result for Another Process, and Use That Result for Another Process

CompletableFuture

package com.devkuma.basic.completablefuture;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class RaceAndConsume2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Supplier<Integer> initValueSupplier = () -> 100;
        Supplier<Integer> anotherValueSupplier = () -> 200;
        Function<Integer, Integer> multiply = value -> value * 2;
        Consumer<Integer> valueConsumer = value -> System.out.println(value);

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(initValueSupplier);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(anotherValueSupplier);

        future1.applyToEitherAsync(future2, multiply)
               .thenAcceptAsync(valueConsumer)
               .get();
    }
}

Result:

200 or 400