Turning Asynchronous Calls into Synchronous Responses with CompletableFuture in Java

This article demonstrates how to convert multiple asynchronous queries into a synchronous response using Java's CompletableFuture, providing step‑by‑step code examples in both Java and Groovy, explaining execution flow, timeout handling, and practical considerations for WebSocket‑like scenarios.

FunTester
FunTester
FunTester
Turning Asynchronous Calls into Synchronous Responses with CompletableFuture in Java

Background

Earlier we used java.util.concurrent.CountDownLatch to turn multiple asynchronous queries into a synchronous response by creating a latch per task, waiting for all tasks, then assembling the result.

Using CompletableFuture

java.util.concurrent.CompletableFuture

provides a more flexible way to compose asynchronous operations, especially when a request must wait for another async task before producing a response (e.g., a WebSocket message exchange).

Java example

The program below creates a CompletableFuture, completes it in a separate thread after a delay, and retrieves the result with a timeout.

import com.funtester.frame.SourceCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class PerFunTest extends SourceCode {
    private static final Logger log = LogManager.getLogger(PerFunTest.class);

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        log.info("Test start");
        CompletableFuture<String> future = new CompletableFuture<>();

        // Run async task in a new thread
        new Thread(() -> {
            // Simulate work
            try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
            future.complete("FunTester");
            log.info("Value assigned");
        }).start();

        // Wait up to 5 seconds for the result
        String result = future.get(5, TimeUnit.SECONDS);
        if (result != null) {
            log.info("Retrieved value: {}", result);
        }
    }
}

Console output shows the start log, the value retrieval, and the completion log occurring almost simultaneously, confirming that the asynchronous task finished before the timeout. If the simulated delay exceeds the timeout, future.get(...) throws a TimeoutException, which must be handled to avoid blocking the request indefinitely.

Groovy example

The same logic expressed in Groovy demonstrates language‑agnostic usage on the JVM.

import com.funtester.frame.SourceCode
import groovy.util.logging.Log4j2
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit

@Log4j2
class Ts extends SourceCode {
    static void main(String[] args) {
        log.info("Test start")
        def future = new CompletableFuture<String>()

        // Asynchronous block
        Thread.start {
            try { Thread.sleep(1000) } catch (InterruptedException ignored) {}
            future.complete("FunTester")
            log.info("Value assigned")
        }

        String result = future.get(5, TimeUnit.SECONDS)
        if (result != null) {
            log.info("Retrieved value: $result")
        }
    }
}

Key points and caveats

Use future.complete(value) to signal successful completion; alternatively future.completeExceptionally(e) can propagate errors.

The timeout passed to future.get(timeout, unit) should be chosen based on expected async latency and overall request latency requirements.

When the timeout expires, a TimeoutException is thrown; callers should catch it and decide whether to return a fallback response or retry. CompletableFuture can be chained with methods such as thenApply, thenCompose, and whenComplete for more complex async workflows.

Further reading

Explore other classes in java.util.concurrent (e.g., ExecutorService, ScheduledThreadPoolExecutor) to build robust asynchronous pipelines.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentconcurrencyCompletableFutureasynchronous programming
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.