Java CompletableFuture

What is Java CompletableFuture?

Java CompletableFuture is used when you have a task that needs to be run asynchronously and you want to wait for the task to complete and obtain a value OR to force a task to complete. It also allows chaining multiple tasks together or executing a task after a few other tasks are completed. The power of Java CompletableFuture lies in its ability to Join tasks in multiple ways thereby providing a high level of flexibility in organizing asynchronous tasks.
It has been added in Java 1.8 and extends the functionality of Future.

Example of creating a Java CompletableFuture.

Java CompletableFuture can be created by the following methods:

Creating an empty Java CompletableFuture

Create an empty CompletableFuture and add stages to it. It is incomplete and can be completed.

CompletableFuture completableFuture = new CompletableFuture<>();

Create an async Java CompletableFuture using a Supplier.

Create a completableFuture Using a Supplier<U>. The supplier provides the return value and the CompletableFuture is asynchronously completed.

CompletableFuture completableFutureSupplyAsync = CompletableFuture.supplyAsync(() -> {
     return Thread.currentThread().getName();
});
System.out.println(completableFutureSupplyAsync.get());// Prints ForkJoinPool.commonPool-worker-1

By Default the async process uses the ForkJoinPool, however we can pass our own Executor framework.

Executor executor = Executors.newCachedThreadPool();
CompletableFuture completetableFutureSupplyAsyncWithCustomExecutor = CompletableFuture
		.supplyAsync(() -> {
			return Thread.currentThread().getName();
		}, executor);
System.out.println(completetableFutureSupplyAsyncWithCustomExecutor.get());// prints pool-1-thread-1

Create an async Java CompletableFuture Using a Runnable.

If you don’t need to return a value, use the runAsync method instead. It takes in a Runnable

CompletableFuture completetableFutureRunAsync = CompletableFuture.runAsync(() -> {
	System.out.println(Thread.currentThread().getName());
});

Ways to complete a Java CompletableFuture.

CompletableFuture allows you to complete a task explicitly from the calling thread. It is possible to complete a task cleanly or to complete a task to through an Exception. Let’s look at some of the ways to complete.

complete a Java CompletableFuture without exception

Lets first see how to cleanly complete a CompletableFuture. In the example below we first create a CompletableFuture using the supplyAsync method and we put a sleep of 10 seconds inside the async task. We then complete the future externally. Since there is a sleep of 10s the internal task would not have a chance to complete and it will never return the value of “finished”. A ‘get()’ call simply returns the value that we pass to the complete() function and isDone() returns true.

CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
	try {
		Thread.sleep(10000);
	} catch (InterruptedException e) {
		System.out.println("Interrupted");
	}
	return "finished";
});
// complete it
completableFuture.complete("Explicitly Finished");
try {

	System.out.println(completableFuture.get());// prints "Explicitly Finished"
} catch (InterruptedException | ExecutionException e) {
	// no exception thrown
	e.printStackTrace();
}
System.out.println(completableFuture.isDone());// prints true

complete a Java CompletableFuture with exception

In the previous example, we completed the CompletableFuture so that it just returns without throwing an exception, in this example we tell the async task to complete by throwing an Exception.

CompletableFuture completableFutureException = CompletableFuture.supplyAsync(() -> {
	try {
		Thread.sleep(10000);
	} catch (InterruptedException e) {
		System.out.println("Interrupted");
	}
	return "finished";
});

completableFutureException.completeExceptionally(new Exception("Explicitly Completed"));
try {
	System.out.println(completableFutureException.get());
} catch (InterruptedException | ExecutionException e) {
	System.out.println(e.getMessage()); // prints java.lang.Exception: Explicitly Completed
}
System.out.println(completableFutureException.isCancelled());// prints false
System.out.println(completableFutureException.isCompletedExceptionally());// prints true

CompletionStage methods of Java CompletableFuture – Chaining Tasks

CompletableFuture implements the CompletionStage interface and this interface provides a group of methods to arrange CompletableFuture Tasks. Lets look at some of the methods

Perform an action when one of two CompletableFuture completes

If there are two CompletableFutures and we need to perform an action with the first action that completes, then use acceptEither. Has an async variation.

CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
	return "Process 1";
});
CompletableFuture otherCompletableFuture = CompletableFuture.supplyAsync(() -> {
	try {
		Thread.sleep(1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	return "Process 2";
});
completableFuture.acceptEither(otherCompletableFuture, (s) -> {
	System.out.println(s); // prints "Process 1"
});

The applyToEither variation performs an operation on the result and returns the result of that operation.

System.out.println(completableFuture.applyToEither(otherCompletableFuture, (s) -> {
	return s.toLowerCase();
}).get()); // print "process 1"

Handle both the result and Exception from a CompletableFuture

The CompletableFuture may either complete without problems or throw and Exception. Use the ‘handle’ method to handle both cases together.

CompletableFuture stage1 = completableFuture.handle((s, e) -> {
	if (e != null)
		System.out.println(e.getMessage());
	return s.toLowerCase();
});
System.out.println(stage1.get()); // prints process1

Perform a Task after two CompletableFuture finish

CompletableFuture completableFuture = CompletableFuture.runAsync(() -> {
	System.out.println("Process 1");
});
CompletableFuture otherCompletableFuture = CompletableFuture.runAsync(() -> {
	try {
		Thread.sleep(1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	System.out.println("Process 2");
});
// use the runAfterBoth method to execute a Runnable after execution of two CompletableFutures
completableFuture.runAfterBoth(otherCompletableFuture, () -> {
	System.out.println("After process 1 and process 2");
}).get(); // prints "Process 1" followed by "Process 2" and then "After process 1 and process 2"

Perform a Task that accepts the results of two CompletableFutures

CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
	return "Process 1";
});
CompletableFuture otherCompletableFuture = CompletableFuture.supplyAsync(() -> {
	try {
		Thread.sleep(1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	return "Process 2";
});
// use the runAfterBoth method to execute a Runnable after execution of two
// CompletableFutures
completableFuture.thenAcceptBoth(otherCompletableFuture, (s1, s2) -> {
	System.out.println("result of 1 : " + s1);
	System.out.println("result of 2 : " + s2);
	System.out.println("After process 1 and process 2");
}).get();
/*-
 * prints
 * result of 1 : Process 1
   result of 2 : Process 2
   After process 1 and process 2
 */

Return a CompletableFuture when any one of a group of CompletableFuture complete

CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
	return "Process 1";
});
CompletableFuture otherCompletableFuture = CompletableFuture.supplyAsync(() -> {
	try {
		Thread.sleep(1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	return "Process 2";
});
// returns a completablefuture when any one of the two completes
System.out.println(CompletableFuture.anyOf(completableFuture, otherCompletableFuture).get());// prints Process 1

Return a CompletableFuture when All of a group of CompletableFuture complete

This method can be used to wait till all the asynchronous processes complete. No results are
returned

CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
	return "Process 1";
});
CompletableFuture otherCompletableFuture = CompletableFuture.supplyAsync(() -> {
	try {
		Thread.sleep(1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	return "Process 2";
});
System.out.println(CompletableFuture.allOf(completableFuture, otherCompletableFuture).get()); // prints null

We have described some of the methods of Java CompletableFuture. To look at the complete list, check out the CompletionStage docs.

Leave a Comment