← Back to Advanced Java Course | Chapter 11: Advanced & Security | Lesson 7 of 19

Java CompletableFuture

What is CompletableFuture?

CompletableFuture, introduced in Java 8, supports asynchronous, non-blocking programming by running tasks on a background thread and letting you attach callbacks that fire when the result is ready, instead of blocking the calling thread to wait for it.

Example: What is CompletableFuture?

java
import java.util.concurrent.CompletableFuture;
public class Main {
	public static void main(String[] args) throws Exception {
		CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "done"); // runs in background
		future.thenAccept(result -> System.out.println("Callback fired: " + result));
		future.get(); // wait so main() doesn't exit before the callback runs
	}
}

Running Tasks with runAsync()

The static runAsync() method executes a task asynchronously on a background thread pool. It accepts a Runnable and returns no result, which makes it the right choice for fire-and-forget side effects like logging or sending a notification.

Example: Running Tasks with runAsync()

java
import java.util.concurrent.CompletableFuture;
public class Main {
	public static void main(String[] args) throws Exception {
		CompletableFuture<Void> future = CompletableFuture.runAsync(() -> System.out.println("Fire-and-forget task")); // no result
		future.get();
	}
}

SupplyAsync and Callbacks

supplyAsync() runs a task that produces a result asynchronously, and chaining its output to thenAccept() lets you process that completed value once it's ready, all without blocking the calling thread at any point in the chain.

Example: SupplyAsync and Callbacks

java
import java.util.concurrent.CompletableFuture;
public class Main {
	public static void main(String[] args) throws Exception {
		CompletableFuture.supplyAsync(() -> 10 * 5)
			.thenAccept(result -> System.out.println("Result: " + result))
			.get(); // never blocks until this final wait
	}
}

Combining Futures

thenCombine() lets you combine two independent CompletableFutures and process both of their results together as soon as they've both finished, which is useful when two unrelated async operations (like two separate API calls) both need to complete before you can proceed.

Example: Combining Futures

java
import java.util.concurrent.CompletableFuture;
public class Main {
	public static void main(String[] args) throws Exception {
		CompletableFuture<Integer> a = CompletableFuture.supplyAsync(() -> 5);
		CompletableFuture<Integer> b = CompletableFuture.supplyAsync(() -> 10);
		CompletableFuture<Integer> combined = a.thenCombine(b, (x, y) -> x + y);
		System.out.println(combined.get());
	}
}

Exception Handling

exceptionally() lets you catch and handle errors thrown anywhere inside a chain of async background tasks, letting you supply a safe fallback value instead of the exception propagating uncaught and silently failing the whole async pipeline.

Example: Exception Handling

java
import java.util.concurrent.CompletableFuture;
public class Main {
	public static void main(String[] args) throws Exception {
		CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
			throw new RuntimeException("task failed");
		}).exceptionally(ex -> -1); // safe fallback instead of propagating uncaught
		System.out.println(future.get());
	}
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.