Posted on: January 21, 2025 Posted by: rahulgite Comments: 0

CompletableFuture is part of Java’s java.util.concurrent package introduced in Java 8. It is a powerful tool for asynchronous programming, allowing non-blocking execution, chaining of tasks, and efficient handling of results and exceptions.


Key Features of CompletableFuture

  1. Asynchronous Execution:
    • Execute tasks in separate threads without blocking the main thread.
  2. Task Chaining:
    • Chain multiple stages of computation using methods like thenApply, thenAccept, or thenCompose.
  3. Exception Handling:
    • Handle exceptions gracefully using methods like exceptionally and handle.
  4. Combining Futures:
    • Combine multiple CompletableFuture instances using thenCombine, thenAcceptBoth, allOf, or anyOf.
  5. Non-blocking API:
    • Unlike Future, CompletableFuture provides methods to retrieve results asynchronously.

Key Methods in CompletableFuture

MethodDescription
supplyAsync(Supplier)Executes a Supplier task asynchronously and returns a CompletableFuture.
runAsync(Runnable)Executes a Runnable task asynchronously without returning a result.
thenApply(Function)Transforms the result of a previous computation.
thenAccept(Consumer)Consumes the result without returning a value.
thenCompose(Function)Chains dependent asynchronous tasks.
thenCombine(BiFunction)Combines the result of two futures.
thenAcceptBoth(Consumer)Consumes results of two futures.
exceptionally(Function)Handles exceptions in a computation.
allOf(CompletableFuture)Combines multiple futures and completes when all are done.
anyOf(CompletableFuture)Completes when any one of the futures completes.
handle(BiFunction)Handles both result and exception.

Scenarios Where CompletableFuture is Useful

  1. Asynchronous API Calls:
    • For executing tasks like HTTP requests without blocking the main thread.
  2. Data Processing Pipelines:
    • Chain multiple stages of data processing.
  3. Combining Multiple Results:
    • Combine results from different asynchronous computations.
  4. Graceful Error Handling:
    • Handle and recover from errors without interrupting the flow.
  5. Parallel Task Execution:
    • Execute independent tasks concurrently and synchronize them efficiently.

Example 1: Basic CompletableFuture

Code:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureBasicExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> "Hello")
                .thenApply(result -> result + " World!")
                .thenAccept(System.out::println);
    }
}

Explanation:

  • supplyAsync: Asynchronously provides the string “Hello”.
  • thenApply: Transforms the result by appending ” World!”.
  • thenAccept: Consumes the final result and prints it.

Output:

Hello World!

Example 2: Running a Task Without a Result

Code:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureRunAsyncExample {
    public static void main(String[] args) {
        CompletableFuture.runAsync(() -> {
            System.out.println("Task is running asynchronously.");
        });
    }
}

Explanation:

  • runAsync: Executes a task asynchronously without returning any result.

Output:

Task is running asynchronously.

Example 3: Handling Exceptions

Code:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExceptionHandling {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            if (Math.random() > 0.5) {
                throw new RuntimeException("Something went wrong!");
            }
            return "Success";
        }).exceptionally(ex -> {
            System.out.println("Error: " + ex.getMessage());
            return "Default Value";
        }).thenAccept(System.out::println);
    }
}

Explanation:

  • supplyAsync: Executes a task that may throw an exception.
  • exceptionally: Catches the exception and provides a default value.
  • thenAccept: Consumes the final result and prints it.

Output (Randomized):

Error: Something went wrong!
Default Value

or

Success

Example 4: Combining Results of Two Futures

Code:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureCombineExample {
    public static void main(String[] args) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

        future1.thenCombine(future2, (result1, result2) -> result1 + " " + result2)
               .thenAccept(System.out::println);
    }
}

Explanation:

  • future1 and future2: Asynchronously compute two strings.
  • thenCombine: Combines the results of both futures.
  • thenAccept: Prints the combined result.

Output:

Hello World

Example 5: Running Tasks in Parallel

Code:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureParallelExample {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            System.out.println("Task 1 running");
        });

        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
            System.out.println("Task 2 running");
        });

        CompletableFuture<Void> combined = CompletableFuture.allOf(future1, future2);
        combined.join();
    }
}

Explanation:

  • runAsync: Executes two tasks asynchronously.
  • allOf: Waits for both tasks to complete.
  • join: Blocks the main thread until all tasks are done.

Output:

Task 1 running
Task 2 running

Example 6: Handling Both Results and Exceptions

Code:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureHandleExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            if (Math.random() > 0.5) {
                throw new RuntimeException("Something failed");
            }
            return "Success";
        }).handle((result, ex) -> {
            if (ex != null) {
                return "Handled error: " + ex.getMessage();
            } else {
                return "Result: " + result;
            }
        }).thenAccept(System.out::println);
    }
}

Explanation:

  • handle: Processes both successful results and exceptions.
  • thenAccept: Prints the final outcome.

Output (Randomized):

Handled error: Something failed

or

Result: Success

Advantages of CompletableFuture

  1. Non-blocking API:
    • Enables better resource utilization compared to blocking threads.
  2. Efficient Chaining:
    • Reduces boilerplate code for chaining dependent tasks.
  3. Integrated Exception Handling:
    • Gracefully manage errors and fallback logic.
  4. Parallelism:
    • Execute multiple independent tasks in parallel.

Disadvantages of CompletableFuture

  1. Complexity:
    • Managing complex workflows with multiple tasks can lead to nested and hard-to-read code.
  2. Debugging:
    • Debugging asynchronous code can be more challenging than synchronous code.

Summary

CompletableFuture is a robust and versatile tool for asynchronous programming in Java. Its ability to handle complex workflows with minimal blocking makes it an essential component of modern Java applications. By leveraging its features, developers can write efficient and scalable asynchronous code.

Leave a Comment