20 Practical Examples of Using Java CompletableFuture for Asynchronous Programming
This article introduces Java's CompletableFuture and CompletionStage APIs, explains their contracts, and provides twenty concise code examples that demonstrate creating completed futures, running async stages, applying functions synchronously and asynchronously, handling exceptions, combining futures, using custom executors, and orchestrating multiple stages with allOf/anyOf.
Java's CompletableFuture implements the CompletionStage interface and offers a powerful, standard‑library way to write asynchronous code without external frameworks such as RxJava.
Creating a completed future
static void completedFutureExample() {
CompletableFuture cf = CompletableFuture.completedFuture("message");
assertTrue(cf.isDone());
assertEquals("message", cf.getNow(null));
}Running a simple async stage
static void runAsyncExample() {
CompletableFuture cf = CompletableFuture.runAsync(() -> {
assertTrue(Thread.currentThread().isDaemon());
randomSleep();
});
assertFalse(cf.isDone());
sleepEnough();
assertTrue(cf.isDone());
}Applying a function to a previous stage
static void thenApplyExample() {
CompletableFuture cf = CompletableFuture.completedFuture("message")
.thenApply(s -> {
assertFalse(Thread.currentThread().isDaemon());
return s.toUpperCase();
});
assertEquals("MESSAGE", cf.getNow(null));
}Applying a function asynchronously
static void thenApplyAsyncExample() {
CompletableFuture cf = CompletableFuture.completedFuture("message")
.thenApplyAsync(s -> {
assertTrue(Thread.currentThread().isDaemon());
randomSleep();
return s.toUpperCase();
});
assertNull(cf.getNow(null));
assertEquals("MESSAGE", cf.join());
}Using a custom executor
static ExecutorService executor = Executors.newFixedThreadPool(3, new ThreadFactory() {
int count = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "custom-executor-" + count++);
}
});
static void thenApplyAsyncWithExecutorExample() {
CompletableFuture cf = CompletableFuture.completedFuture("message")
.thenApplyAsync(s -> {
assertTrue(Thread.currentThread().getName().startsWith("custom-executor-"));
assertFalse(Thread.currentThread().isDaemon());
randomSleep();
return s.toUpperCase();
}, executor);
assertNull(cf.getNow(null));
assertEquals("MESSAGE", cf.join());
}Consuming a previous result
static void thenAcceptExample() {
StringBuilder result = new StringBuilder();
CompletableFuture.completedFuture("thenAccept message")
.thenAccept(s -> result.append(s));
assertTrue("Result was empty", result.length() > 0);
}Consuming a previous result asynchronously
static void thenAcceptAsyncExample() {
StringBuilder result = new StringBuilder();
CompletableFuture cf = CompletableFuture.completedFuture("thenAcceptAsync message")
.thenAcceptAsync(s -> result.append(s));
cf.join();
assertTrue("Result was empty", result.length() > 0);
}Completing a stage exceptionally
static void completeExceptionallyExample() {
CompletableFuture cf = CompletableFuture.completedFuture("message")
.thenApplyAsync(String::toUpperCase, CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS));
CompletableFuture exceptionHandler = cf.handle((s, th) -> th != null ? "message upon cancel" : "");
cf.completeExceptionally(new RuntimeException("completed exceptionally"));
assertTrue(cf.isCompletedExceptionally());
try { cf.join(); fail("Should have thrown an exception"); }
catch (CompletionException ex) { assertEquals("completed exceptionally", ex.getCause().getMessage()); }
assertEquals("message upon cancel", exceptionHandler.join());
}Cancelling a computation
static void cancelExample() {
CompletableFuture cf = CompletableFuture.completedFuture("message")
.thenApplyAsync(String::toUpperCase, CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS));
CompletableFuture cf2 = cf.exceptionally(t -> "canceled message");
assertTrue(cf.cancel(true));
assertTrue(cf.isCompletedExceptionally());
assertEquals("canceled message", cf2.join());
}Applying a function to whichever of two futures completes first
static void applyToEitherExample() {
String original = "Message";
CompletableFuture cf1 = CompletableFuture.completedFuture(original)
.thenApplyAsync(s -> delayedUpperCase(s));
CompletableFuture cf2 = cf1.applyToEither(
CompletableFuture.completedFuture(original).thenApplyAsync(s -> delayedLowerCase(s)),
s -> s + " from applyToEither");
assertTrue(cf2.join().endsWith(" from applyToEither"));
}Combining results of two stages
static void thenCombineExample() {
String original = "Message";
CompletableFuture cf = CompletableFuture.completedFuture(original)
.thenApply(s -> delayedUpperCase(s))
.thenCombine(CompletableFuture.completedFuture(original).thenApply(s -> delayedLowerCase(s)),
(s1, s2) -> s1 + s2);
assertEquals("MESSAGEmessage", cf.getNow(null));
}Composing dependent stages
static void thenComposeExample() {
String original = "Message";
CompletableFuture cf = CompletableFuture.completedFuture(original)
.thenApply(s -> delayedUpperCase(s))
.thenCompose(upper -> CompletableFuture.completedFuture(original)
.thenApply(s -> delayedLowerCase(s))
.thenApply(s -> upper + s));
assertEquals("MESSAGEmessage", cf.join());
}Running an action after any of several futures completes
static void anyOfExample() {
StringBuilder result = new StringBuilder();
List<String> messages = Arrays.asList("a", "b", "c");
List<CompletableFuture> futures = messages.stream()
.map(msg -> CompletableFuture.completedFuture(msg).thenApply(s -> delayedUpperCase(s)))
.collect(Collectors.toList());
CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0]))
.whenComplete((res, th) -> {
if (th == null) { assertTrue(isUpperCase((String) res)); result.append(res); }
});
assertTrue(result.length() > 0);
}Running an action after all futures complete (sync and async)
static void allOfExample() {
StringBuilder result = new StringBuilder();
List<String> messages = Arrays.asList("a", "b", "c");
List<CompletableFuture> futures = messages.stream()
.map(msg -> CompletableFuture.completedFuture(msg).thenApply(s -> delayedUpperCase(s)))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.whenComplete((v, th) -> { futures.forEach(cf -> assertTrue(isUpperCase(cf.getNow(null)))); result.append("done"); });
assertTrue(result.length() > 0);
}
static void allOfAsyncExample() {
StringBuilder result = new StringBuilder();
List<String> messages = Arrays.asList("a", "b", "c");
List<CompletableFuture> futures = messages.stream()
.map(msg -> CompletableFuture.completedFuture(msg).thenApplyAsync(s -> delayedUpperCase(s)))
.collect(Collectors.toList());
CompletableFuture all = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.whenComplete((v, th) -> { futures.forEach(cf -> assertTrue(isUpperCase(cf.getNow(null)))); result.append("done"); });
all.join();
assertTrue(result.length() > 0);
}Real‑world scenario: fetching a list of cars, rating each asynchronously, and processing when all are done
cars().thenCompose(cars -> {
List<CompletionStage> updatedCars = cars.stream()
.map(car -> rating(car.manufacturerId).thenApply(r -> { car.setRating(r); return car; }))
.collect(Collectors.toList());
CompletableFuture done = CompletableFuture.allOf(updatedCars.toArray(new CompletableFuture[updatedCars.size()]));
return done.thenApply(v -> updatedCars.stream()
.map(CompletionStage::toCompletableFuture)
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}).whenComplete((cars, th) -> {
if (th == null) { cars.forEach(System.out::println); }
else { throw new RuntimeException(th); }
}).toCompletableFuture().join();The article concludes with references to original sources and notes that the content is reproduced from various blogs and articles about Java CompletableFuture.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
