Optimizing API Execution Time Using CompletableFuture.runAsync() in Spring Boot π

Introduction
Performance optimization is crucial in modern applications. Recently, I encountered a situation where storing API execution details synchronously in the database caused significant delays in response time. To overcome this, I leveraged CompletableFuture.runAsync(), allowing the database operations to run asynchronously.
In this blog, I'll demonstrate how we can optimize API request execution using a real-world example and reduce processing time effectively.
π Problem Statement: Synchronous DB Calls
Imagine you're building an API request execution service where each API call logs its start and end time into the database. Initially, our code looked like this:
LocalDateTime startTime = LocalDateTime.now();
execution.setStartTime(startTime.format(formatter));
apiRequestExecutionRepository.save(execution);
try {
ResponseEntity<Object> responseEntity = restTemplate.exchange(
resolvedUrl,
HttpMethod.valueOf(request.getMethodName()),
requestEntity,
Object.class);
LocalDateTime endTime = LocalDateTime.now();
execution.setEndTime(endTime.format(formatter));
execution.setStatus("C");
apiRequestExecutionRepository.save(execution); // Blocking Call
} catch (Exception e) {
execution.setStatus("E");
apiRequestExecutionRepository.save(execution);
}
This method blocks execution while waiting for the database to store the data, leading to unnecessary delays.
β‘ Solution: Using CompletableFuture.runAsync()
To make our database operations non-blocking, we introduced CompletableFuture.runAsync(). This allows the API execution to continue without waiting for the database save operation to complete.
β Optimized Code
LocalDateTime startTime = LocalDateTime.now();
execution.setStartTime(startTime.format(formatter));
// Save execution asynchronously to avoid blocking
CompletableFuture.runAsync(() -> apiRequestExecutionRepository.save(execution));
try {
ResponseEntity<Object> responseEntity = restTemplate.exchange(
resolvedUrl,
HttpMethod.valueOf(request.getMethodName()),
requestEntity,
Object.class);
LocalDateTime endTime = LocalDateTime.now();
execution.setEndTime(endTime.format(formatter));
execution.setStatus("C");
CompletableFuture.runAsync(() -> apiRequestExecutionRepository.save(execution)); // Async DB Save
} catch (Exception e) {
execution.setStatus("E");
CompletableFuture.runAsync(() -> apiRequestExecutionRepository.save(execution));
}
π Real-World Example: Ordering System
Imagine an e-commerce checkout system where users place orders, and the order details are stored in a database. If we perform a synchronous database save, the checkout button remains unresponsive until the DB operation is complete. However, by saving order details asynchronously, the checkout process feels instant, improving user experience.
Without CompletableFuture (Slow Checkout)
public void placeOrder(Order order) {
orderRepository.save(order); // Blocking call
System.out.println("Order placed successfully!");
}
With CompletableFuture (Fast Checkout)
public void placeOrder(Order order) {
CompletableFuture.runAsync(() -> orderRepository.save(order)); // Non-blocking
System.out.println("Order placed successfully!"); // Runs instantly
}
π― Key Takeaways
β Improved Performance: API execution is faster as database operations donβt block the main thread. β Better User Experience: The system remains responsive, reducing perceived latency. β Scalability: Async execution allows handling more requests efficiently without increasing server load.
π Conclusion
Using CompletableFuture.runAsync()
, we successfully optimized our API execution time by eliminating blocking database calls. This simple yet powerful technique can significantly enhance the performance of your applications. π
Have you faced similar challenges in your projects? Drop your thoughts in the comments! π‘
π Follow me on Hashnode for more backend performance tips! π
Subscribe to my newsletter
Read articles from Sahil Sudan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
