0
0
JavaHow-ToBeginner · 3 min read

How to Use CompletableFuture in Java for Async Programming

Use CompletableFuture in Java to run tasks asynchronously by creating futures with methods like runAsync or supplyAsync. You can then attach callbacks or combine futures to handle results without blocking the main thread.
📐

Syntax

CompletableFuture lets you run tasks asynchronously and handle their results later. You create a CompletableFuture using methods like runAsync for tasks without results or supplyAsync for tasks that return a value. You can then use methods like thenApply, thenAccept, or thenCompose to process the results.

  • CompletableFuture.runAsync(Runnable task): Runs a task without returning a result.
  • CompletableFuture.supplyAsync(Supplier supplier): Runs a task that returns a result.
  • thenApply(Function fn): Transforms the result when ready.
  • thenAccept(Consumer action): Consumes the result without returning anything.
  • thenCompose(Function> fn): Chains futures.
java
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
    // code to run asynchronously
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    // code that returns a String result
    return "Hello";
});

future2.thenApply(result -> result + " World")
       .thenAccept(System.out::println);
💻

Example

This example shows how to run a task asynchronously that returns a value, then transform and print the result without blocking the main thread.

java
import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // Simulate a long-running task
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Hello";
        });

        future.thenApply(result -> result + " World")
              .thenAccept(System.out::println);

        System.out.println("Task started");

        // Wait for the async task to finish to see output
        future.join();
    }
}
Output
Task started Hello World
⚠️

Common Pitfalls

Common mistakes when using CompletableFuture include:

  • Not waiting for the future to complete, causing the program to exit before async tasks finish.
  • Blocking the main thread unnecessarily, which defeats the purpose of async programming.
  • Ignoring exceptions thrown in async tasks, which can cause silent failures.

Always handle exceptions with exceptionally or handle, and use join() or get() to wait if needed.

java
import java.util.concurrent.CompletableFuture;

public class PitfallExample {
    public static void main(String[] args) {
        // Wrong: Not waiting for completion
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(500);
                System.out.println("Async task done");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        System.out.println("Main thread ends");

        // Right: Wait for completion
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(500);
                System.out.println("Async task done");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        future.join();
        System.out.println("Main thread ends after async task");
    }
}
Output
Main thread ends Async task done Async task done Main thread ends after async task
📊

Quick Reference

MethodDescription
runAsync(Runnable)Run a task asynchronously without returning a result.
supplyAsync(Supplier)Run a task asynchronously that returns a result.
thenApply(Function)Transform the result when ready.
thenAccept(Consumer)Consume the result without returning anything.
thenCompose(Function>)Chain dependent futures.
exceptionally(Function)Handle exceptions and provide fallback.
join()Wait for completion and get the result or throw unchecked exception.

Key Takeaways

Use CompletableFuture to run tasks asynchronously without blocking the main thread.
Create futures with runAsync for void tasks and supplyAsync for tasks returning results.
Chain and transform results using thenApply, thenAccept, and thenCompose.
Always handle exceptions in async tasks to avoid silent failures.
Use join() or get() to wait for completion when necessary.