CompletableFuture for async programming

David Kumar Jan 2026
1 tab
package com.example.demo.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

@Service
public class AsyncService {

    private final ExecutorService executor = Executors.newFixedThreadPool(10);

    // Simple async operation
    public CompletableFuture<String> fetchUserData(Long userId) {
        return CompletableFuture.supplyAsync(() -> {
            // Simulate API call
            sleep(1000);
            return "User data for ID: " + userId;
        }, executor);
    }

    // Chain operations
    public CompletableFuture<String> processUserData(Long userId) {
        return fetchUserData(userId)
            .thenApply(data -> data.toUpperCase())
            .thenApply(data -> "Processed: " + data);
    }

    // Combine multiple futures
    public CompletableFuture<String> getUserProfile(Long userId) {
        CompletableFuture<String> userFuture = fetchUserData(userId);
        CompletableFuture<String> postsFuture = fetchUserPosts(userId);
        CompletableFuture<String> friendsFuture = fetchUserFriends(userId);

        return userFuture.thenCombine(postsFuture, (user, posts) ->
                user + ", " + posts)
            .thenCombine(friendsFuture, (combined, friends) ->
                combined + ", " + friends);
    }

    // Sequential composition
    public CompletableFuture<String> getRecommendations(Long userId) {
        return fetchUserData(userId)
            .thenCompose(userData -> {
                // Use userData to fetch recommendations
                return CompletableFuture.supplyAsync(() -> {
                    sleep(500);
                    return "Recommendations based on: " + userData;
                }, executor);
            });
    }

    // Exception handling
    public CompletableFuture<String> fetchWithFallback(Long userId) {
        return fetchUserData(userId)
            .exceptionally(ex -> {
                System.err.println("Error fetching user: " + ex.getMessage());
                return "Default user data";
            });
    }

    // Handle both success and failure
    public CompletableFuture<String> fetchWithHandling(Long userId) {
        return fetchUserData(userId)
            .handle((result, ex) -> {
                if (ex != null) {
                    return "Error: " + ex.getMessage();
                }
                return result;
            });
    }

    // Wait for all futures
    public CompletableFuture<List<String>> fetchMultipleUsers(List<Long> userIds) {
        List<CompletableFuture<String>> futures = userIds.stream()
            .map(this::fetchUserData)
            .collect(Collectors.toList());

        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList()));
    }

    // Race multiple operations
    public CompletableFuture<String> fetchFromFastestSource(Long userId) {
        CompletableFuture<String> db = fetchFromDatabase(userId);
        CompletableFuture<String> cache = fetchFromCache(userId);
        CompletableFuture<String> api = fetchFromAPI(userId);

        return CompletableFuture.anyOf(db, cache, api)
            .thenApply(result -> (String) result);
    }

    // Spring @Async annotation alternative
    @Async
    public CompletableFuture<String> asyncMethod(Long userId) {
        sleep(2000);
        return CompletableFuture.completedFuture("Async result for " + userId);
    }

    // Helper methods
    private CompletableFuture<String> fetchUserPosts(Long userId) {
        return CompletableFuture.supplyAsync(() -> {
            sleep(800);
            return "Posts for user " + userId;
        }, executor);
    }

    private CompletableFuture<String> fetchUserFriends(Long userId) {
        return CompletableFuture.supplyAsync(() -> {
            sleep(600);
            return "Friends of user " + userId;
        }, executor);
    }

    private CompletableFuture<String> fetchFromDatabase(Long userId) {
        return CompletableFuture.supplyAsync(() -> {
            sleep(1500);
            return "DB: User " + userId;
        }, executor);
    }

    private CompletableFuture<String> fetchFromCache(Long userId) {
        return CompletableFuture.supplyAsync(() -> {
            sleep(100);
            return "Cache: User " + userId;
        }, executor);
    }

    private CompletableFuture<String> fetchFromAPI(Long userId) {
        return CompletableFuture.supplyAsync(() -> {
            sleep(2000);
            return "API: User " + userId;
        }, executor);
    }

    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
1 file · java Explain with highlit

CompletableFuture enables non-blocking asynchronous programming in Java. I use supplyAsync() to run tasks in thread pools and thenApply() to chain transformations. thenCompose() flattens nested futures, while thenCombine() merges independent futures. Exception handling uses exceptionally() or handle() to recover from failures. allOf() waits for multiple futures, anyOf() completes when any finishes. CompletableFuture integrates with Spring's @Async for method-level parallelism. Callbacks execute on completion without blocking threads. Custom executors control thread pool behavior. The API supports both callback and imperative styles with join() or get(). CompletableFuture improves throughput by freeing threads during I/O operations, essential for scalable microservices.