# CompletableFuture: A Complete Practical Guide.

`CompletableFuture`, part of Java’s `java.util.concurrent` package, is an incredibly powerful class for managing asynchronous tasks and handling complex workflows. It allows you to create, combine, and handle async operations without blocking threads, making it ideal for building responsive applications.

In this guide, we’ll walk through 10 practical examples that demonstrate how `CompletableFuture` can be used in real-world scenarios. By the end, you’ll have a solid grasp of how to use `CompletableFuture` for concurrent tasks, exception handling, and performance optimization.

---

### 1\. **Simple Asynchronous Task Execution**

A basic example of `CompletableFuture` is running a task asynchronously, such as fetching data from an external source.

```java
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println("Fetching data asynchronously: " + Thread.currentThread().getName());
});
future.join(); // Block and wait for completion
```

**Explanation**: This code runs a task asynchronously on a separate thread, printing a message. `join()` is used to wait for completion, blocking the main thread until the async task finishes.

---

### 2\. **Returning a Result with Async Computation**

Use `supplyAsync` to run a task that returns a result, such as fetching user data.

```java
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
    System.out.println("Fetching user data...");
    return "User Data";
});

String result = userFuture.join(); // Gets "User Data" after async completion
System.out.println("Result: " + result);
```

**Explanation**: This example shows how to run a task that produces a result (`"User Data"`). `supplyAsync` enables returning a value from an async operation.

---

### 3\. **Chaining Async Tasks with thenApply**

Chaining tasks is essential for workflows where the output of one task is needed for the next.

```java
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 50)
        .thenApply(amount -> amount * 2); // Doubles the amount

System.out.println("Processed result: " + future.join());
```

**Explanation**: Here, `thenApply` processes the result of a previous task (doubling a value). It returns a new `CompletableFuture` with the transformed result.

---

### 4\. **Combining Two Futures**

Use `thenCombine` to combine results from two separate async tasks, such as fetching user and profile data.

```java
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> "John");
CompletableFuture<String> profileFuture = CompletableFuture.supplyAsync(() -> "Developer");

CompletableFuture<String> combinedFuture = userFuture.thenCombine(profileFuture, 
    (user, profile) -> user + " - " + profile);

System.out.println("Combined result: " + combinedFuture.join());
```

**Explanation**: This combines results from two futures. `thenCombine` waits for both to complete, then merges their outputs, producing `"John - Developer"`.

---

### 5\. **Handling Errors with Exceptionally**

Handle exceptions in async tasks to provide a fallback or recover gracefully.

```java
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (true) throw new RuntimeException("Failed to fetch data");
    return "Data";
}).exceptionally(ex -> "Fallback Data");

System.out.println("Result: " + future.join());
```

**Explanation**: If an exception is thrown, `exceptionally` provides `"Fallback Data"` as a safe result.

---

### 6\. **Retrying on Failure**

Using recursive CompletableFutures, you can retry a task when it fails.

```java
import java.util.concurrent.CompletableFuture;

public class RetryExample {

    public static CompletableFuture<String> fetchDataWithRetry(int retries) {
        return CompletableFuture.supplyAsync(() -> {
            if (Math.random() < 0.7) throw new RuntimeException("Temporary issue");
            return "Fetched Data";
        }).exceptionally(ex -> retries > 0 ? fetchDataWithRetry(retries - 1).join() : "Failed after retries");
    }

    public static void main(String[] args) {
        String result = fetchDataWithRetry(3).join();
        System.out.println("Result: " + result);
    }
}
```

**Explanation**: If a task fails, `exceptionally` attempts to retry by calling itself recursively with decremented retries.

---

### 7\. **Timeout Management**

Set a timeout to avoid endless waits, useful for external services that may hang.

```java
import java.util.concurrent.*;

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try { Thread.sleep(3000); } catch (InterruptedException e) {}
    return "Result";
});

String result = future.orTimeout(2, TimeUnit.SECONDS)
                      .exceptionally(ex -> "Timeout").join();

System.out.println("Result: " + result);
```

**Explanation**: If the task exceeds the 2-second limit, the `exceptionally` block handles the timeout, returning `"Timeout"`.

---

### 8\. **Parallel Processing with AllOf**

Process multiple tasks in parallel and wait for all to complete.

```java
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Task 3");

CompletableFuture<Void> allOf = CompletableFuture.allOf(future1, future2, future3);

allOf.thenRun(() -> {
    System.out.println(future1.join());
    System.out.println(future2.join());
    System.out.println(future3.join());
}).join();
```

**Explanation**: `allOf` waits for all tasks to complete before proceeding, making it useful for aggregating results from multiple async tasks.

---

### 9\. **AnyOf for First Completed Task**

Use `anyOf` when only one task needs to complete to proceed, ideal for race conditions.

```java
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    try { Thread.sleep(100); } catch (InterruptedException e) {}
    return "First";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Second");

CompletableFuture<Object> anyOf = CompletableFuture.anyOf(future1, future2);

System.out.println("First completed: " + anyOf.join());
```

**Explanation**: `anyOf` completes as soon as any task finishes, making it efficient when only the first result matters.

---

### 10\. **Pipelining Dependent Tasks**

Pipelining allows you to create a sequence of async tasks where each depends on the previous task’s result, useful for multi-step workflows.

```java
CompletableFuture<Void> pipeline = CompletableFuture.supplyAsync(() -> "Stage 1")
        .thenApply(stage1 -> stage1 + " -> Stage 2")
        .thenApply(stage2 -> stage2 + " -> Stage 3")
        .thenAccept(result -> System.out.println("Pipeline result: " + result));

pipeline.join();
```

**Explanation**: This example creates a multi-step async pipeline where each stage builds upon the last, producing `"Stage 1 -> Stage 2 -> Stage 3"` at the end.

---

### Conclusion

`CompletableFuture` provides a powerful framework for managing asynchronous programming in Java, allowing developers to create complex workflows, handle errors gracefully, and optimize performance. With these 10 practical examples, you can start applying `CompletableFuture` to real-world scenarios in your applications to improve responsiveness, scalability, and code readability.

**More such articles:**

[**https://medium.com/techwasti**](https://techwasti.com/Link)

[**https://www.youtube.com/@maheshwarligade**](https://www.youtube.com/@maheshwarligade)

[**https://techwasti.com/series/spring-boot-tutorials**](https://techwasti.com/series/spring-boot-tutorials)

[**https://techwasti.com/series/go-language**](https://techwasti.com/series/go-language)
