CompletableFuture in Java

Admin, Student's Library
0

CompletableFuture in Java: A Complete Guide

CompletableFuture in Java is a powerful class that simplifies asynchronous programming. It was introduced in Java 8 and is an advanced version of the Future interface. With CompletableFuture, we can write non-blocking code, which improves application performance.

In this guide, we’ll explore CompletableFuture in detail, understand its key methods, and look at examples of when and how to use it.


1. What is CompletableFuture?

CompletableFuture is a class that implements both Future and CompletionStage interfaces. It allows us to:

  1. Run asynchronous tasks.

  2. Add callbacks once a task is finished.

  3. Combine multiple futures.

  4. Perform exception handling in asynchronous flows.


2. Ways to Create CompletableFuture

(1) runAsync() – Runs a task without returning a result (Runnable)

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { System.out.println("Task running asynchronously in: " + Thread.currentThread().getName()); }); future.get(); // Waits for the result (blocks)

Output:

Task running asynchronously in: ForkJoinPool.commonPool-worker-1

(2) supplyAsync() – Runs a task and returns a result (Supplier)

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { return "Hello from CompletableFuture!"; }); System.out.println(future.get());

Output:

Hello from CompletableFuture!

3. Key Methods of CompletableFuture

(1) thenApply() – Transforms the result

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Java") .thenApply(name -> "Hello, " + name); System.out.println(future.get()); // Hello, Java

(2) thenAccept() – Consumes the result (no return value)

CompletableFuture.supplyAsync(() -> "Java") .thenAccept(result -> System.out.println("Received: " + result)); // Received: Java

(3) thenRun() – Runs an action after completion

CompletableFuture.supplyAsync(() -> "Java") .thenRun(() -> System.out.println("Task Completed!")); // Task Completed!

(4) thenCompose() – Chains multiple futures

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello") .thenCompose(result -> CompletableFuture.supplyAsync(() -> result + " World")); System.out.println(future.get()); // Hello World

(5) thenCombine() – Combines results of two futures

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World"); CompletableFuture<String> combined = future1.thenCombine(future2, (res1, res2) -> res1 + " " + res2); System.out.println(combined.get()); // Hello World

(6) allOf() – Waits for all futures to complete

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1"); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2"); CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Task 3"); CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2, future3); allFutures.get(); // Waits until all tasks are complete

(7) anyOf() – Proceeds when any one future completes

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return "Result 1"; }); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Result 2"); CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2); System.out.println(anyFuture.get()); // Result 2 (finishes first)

(8) exceptionally() – Handles errors

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { if (true) throw new RuntimeException("Error Occurred!"); return "Success"; }).exceptionally(ex -> "Handled: " + ex.getMessage()); System.out.println(future.get()); // Handled: java.lang.RuntimeException: Error Occurred!

(9) handle() – Handles both success and failure

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { return "Success"; }).handle((result, ex) -> { if (ex != null) return "Error: " + ex.getMessage(); else return result; }); System.out.println(future.get()); // Success

4. When to Use CompletableFuture?

  • To run asynchronous tasks (e.g., API calls, database operations).

  • For parallel processing with multiple independent tasks.

  • For callback-based workflows.

  • For complex task chaining (where the output of one task becomes the input of another).


5. Full Example: Multiple Asynchronous Tasks

import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { // Task 1: Fetch user name CompletableFuture<String> getUserName = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "Rahul"; }); // Task 2: Fetch user email CompletableFuture<String> getUserEmail = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } return "rahul@example.com"; }); // Combine both results CompletableFuture<String> combinedFuture = getUserName.thenCombine(getUserEmail, (name, email) -> "Name: " + name + ", Email: " + email); System.out.println(combinedFuture.get()); // Output: Name: Rahul, Email: rahul@example.com } }

6. Conclusion

CompletableFuture makes asynchronous programming in Java simpler and more powerful. With it, you can:

✔ Write non-blocking code.
✔ Combine multiple tasks easily.
✔ Handle exceptions gracefully.
✔ Add flexible callbacks for task completion.

It is widely useful in multithreaded applications, REST API calls, and database operations.

Post a Comment

0 Comments
* Please Don't Spam Here. All the Comments are Reviewed by Admin.
Post a Comment (0)
Our website uses cookies to enhance your experience. Learn More
Accept !