2 min de lecturacode

Asynchronous Programming in Java: CompletableFuture vs Reactive Streams

Choosing the Right Tool for Concurrency in Modern Java

Asynchronous Programming in Java: CompletableFuture vs Reactive Streams

Asynchronous programming enables applications to execute non-blocking operations, crucial for performance in modern, concurrent systems. In Java, two powerful tools are commonly used: CompletableFuture and Reactive Streams (e.g., Project Reactor or RxJava). In this post, we’ll compare both approaches through real code examples and output results.

1. CompletableFuture: Java’s Built-in Promise API

CompletableFuture is a class introduced in Java 8 to write asynchronous, non-blocking code. It's suitable for simple async flows and integrates well with imperative codebases.

Example 1: Basic Async Execution

import java.util.concurrent.CompletableFuture;

public class CompletableFutureDemo {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            sleep(1000);
            return "Hello from CompletableFuture!";
        });

        future.thenAccept(System.out::println);
        sleep(1500); // wait for completion
    }

    static void sleep(long millis) {
        try { Thread.sleep(millis); } catch (InterruptedException e) { }
    }
}

Output:

Hello from CompletableFuture!

Example 2: Chaining and Combining

import java.util.concurrent.CompletableFuture;

public class CompletableFutureCombineDemo {
    public static void main(String[] args) {
        CompletableFuture<String> greeting = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> name = CompletableFuture.supplyAsync(() -> "World");

        CompletableFuture<String> result = greeting.thenCombine(name, (g, n) -> g + ", " + n);
        System.out.println(result.join());
    }
}

Output:

Hello, World

Pros and Cons of CompletableFuture

Pros:

  • Native Java API, no dependencies
  • Familiar to imperative developers
  • Easy for linear async flows

Cons:

  • Limited operators compared to reactive libraries
  • Complex error handling in larger flows
  • Not suitable for high-throughput event streams

2. Reactive Streams: Declarative and Event-Driven

Reactive programming models async execution as a stream of data. With libraries like Project Reactor (Mono, Flux) or RxJava, you can build pipelines to transform, filter, and combine async data.

Example 1: Mono (Single Async Value)

import reactor.core.publisher.Mono;
import java.time.Duration;

public class ReactorMonoExample {
    public static void main(String[] args) {
        Mono.just("Hello from Mono!")
            .delayElement(Duration.ofMillis(500))
            .subscribe(System.out::println);

        sleep(1000);
    }

    static void sleep(long millis) {
        try { Thread.sleep(millis); } catch (InterruptedException e) { }
    }
}

Output:

Hello from Mono!

Example 2: Flux (Multiple Async Events)

import reactor.core.publisher.Flux;
import java.time.Duration;

public class ReactorFluxExample {
    public static void main(String[] args) {
        Flux.interval(Duration.ofMillis(300))
            .take(5)
            .map(i -> "Tick " + i)
            .subscribe(System.out::println);

        sleep(2000);
    }

    static void sleep(long millis) {
        try { Thread.sleep(millis); } catch (InterruptedException e) { }
    }
}

Output:

Tick 0
Tick 1
Tick 2
Tick 3
Tick 4

Pros and Cons of Reactive Streams

Pros:

  • Rich operator set for complex flows
  • Built-in backpressure handling
  • Ideal for data streams and event-driven apps

Cons:

  • Requires learning new paradigm
  • Can be overkill for simple use cases
  • More dependencies (Reactor, RxJava)

3. When to Use Each?

Use CaseCompletableFutureReactive Streams
Simple async call✅❌
API call + transform✅✅
Data pipeline (streaming)❌✅
Reactive UI/backend❌✅
Minimal dependencies✅❌
Advanced composition❌✅

Conclusion

Both CompletableFuture and Reactive Streams serve asynchronous needs, but they excel in different areas. For straightforward async calls, CompletableFuture is often enough. For complex, event-driven, and high-throughput systems, reactive programming shines.

By mastering both, you’ll be ready to choose the right tool for every concurrency challenge in modern Java applications.

Further Reading

Autor

Jaime de Arcos

Solutions architect y desarrollador backend.

Ver trayectoria completa

Comentarios