Java: Asynchronous programming using CompletableFuture

Sumant Mishra
3 min readFeb 10, 2023

In this post, I will discuss how to execute a long-running process in asynchronous as non-blocking code.

Let’s consider a scenario where we have to process a long-running task or execute a process-oriented task that may take more than the usual API response time. For example, bulk upload feature where we may need to upload a CSV that contains thousands of users which we need to insert if the user does not exist in the system or update if the user already exists. In such cases, we may not need the API to hold until the process completes. Sometimes, API may throw a timeout error if the process takes more than the usual API response time.

To avoid such scenarios, we generally write non-blocking code and process the long-running tasks on a separate thread from the main application thread. Once the process completes, we notify the user about the process completion.

This can be achieved in many ways like using Java Message Service or any other Queue services provided by AWS, Azure, or any other third-party service provider. But here, we will be talking about CompletableFuture.

CompletableFuture

CompletableFuture is used for asynchronous programming in Java. Asynchronous programming is a means of writing non-blocking code by running a task on a separate thread from the main application thread and notifying the main thread about its progress, completion, or failure.

This way, your main thread does not block/wait for the completion of the task and it can execute other tasks in parallel.

Having this kind of parallelism greatly improves the performance of your programs.

Though CompletableFuture provides many features, I will be discussing runAsync() and sypplyAsync() methods of CompletableFuture.

Running asynchronous processes using runAsync()

This method helps executing long-running processes in the background without blocking the API response in the main thread. If you want to run some background task asynchronously and don’t want to return anything from the task, then you can use CompletableFuture.runAsync() method. It takes a Runnable object and returns CompletableFuture<Void>.

CompletableFuture.runAsync() is useful for tasks that don’t return anything.

@GetMapping("/asynctest")
public String testAsync(){

RestTemplate restTemplate = new RestTemplate();
String url = "https://jsonplaceholder.typicode.com/posts";
String response = "Started!";

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
Date d1 = new Date();
for(int i = 0; i < 1000; i++){
restTemplate.getForObject(url, String.class);
System.out.println("request count: " + i);
}
Date d2 = new Date();
float seconds = ((d2.getTime()-d1.getTime())/1000);
System.out.println("Seconds: " + (seconds/60));
});

return response;
}

Above code will execute the code with CompletableFuture block in a separate thread without holding the API response. Here I am trying to send 1000 HTTP requests in a loop.

Run a task asynchronously and return the result using supplyAsync()

Generally, API throws timeout exception if the API takes more time than a defined time to process the task. supplyAsync() helps holding API response until the background process completes. It has a single get() method where you can write your background task and return the result.

CompletableFuture.runAsync() is useful for tasks that returns a value.

@GetMapping("/asynctest")
public String testAsync(){

RestTemplate restTemplate = new RestTemplate();
String url = "https://jsonplaceholder.typicode.com/posts";
String response = "Started";
System.out.println("Process Started");

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
Date d1 = new Date();
for(int i = 0; i < 1000; i++){
restTemplate.getForObject(url, String.class);
System.out.println("request count: " + i);
}
Date d2 = new Date();
float seconds = ((d2.getTime()-d1.getTime())/1000);
System.out.println("Seconds: " + (seconds/60));
return "Completed";
});
try {
response = future.get();
}
catch(Exception e){
response = e.toString();
}
System.out.println("Process completed");

return response;
}

Above code will execute the code with CompletableFuture block in a separate thread by holding the API response. Here I am trying to send 1000 HTTP requests in a loop. API response should be “Completed” once all the 1000 requests completed.

Hope this helps! Please share your feedback.

For a detailed reference, please read:
https://www.callicoder.com/java-8-completablefuture-tutorial/

--

--

Sumant Mishra

Fullstack Architect || TOGAF 9 || AWSCSAA || Cloud Practitioner || NodeJS || React & Angular || Docker || Coder