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

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
Supplierasynchronously and returns aCompletableFutureinstance.
- Processes the
CompletableFuture.thenAcceptAsync(Consumer)- When processing of the
CompletableFutureinstance finishes, passes the return value and executes the Consumer processing.
- When processing of the
Return a Value as the Result of Processing, Transform and Return It, and Process That Return Value

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
CompletableFutureand executes theFunctionprocessing.
- Passes the result value of the
Execute One Process on Multiple Threads and Use the First Result for Another Process

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
Consumerprocessing.
- Uses whichever result comes first and performs
Execute One Process on Multiple Threads, Use the First Result for Another Process, and Use That Result for Another Process

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