Using CompletableFuture and DeferredResult

In modern web development, it is imperative to handle concurrent requests efficiently. One way to achieve this is by using asynchronous programming techniques. In this article, we will explore how to leverage CompletableFuture and DeferredResult in a Spring Boot application.

CompletableFuture

CompletableFuture is a powerful class introduced in Java 8 that provides the ability to perform asynchronous computations. It allows you to express chained computations and handle the results asynchronously. Let's see how we can use it in our Spring Boot application.

First, we need to define our asynchronous operation. This can be done by creating a method that returns a CompletableFuture inside a service class. For example, let's say we have a REST endpoint that retrieves a user's details from a remote service. We can define the following method in our service class:

@Service
public class UserService {

  public CompletableFuture<UserDetails> fetchUserDetails(String userId) {
    return CompletableFuture.supplyAsync(() -> {
      // Perform the remote service call
      UserDetails userDetails = remoteService.getUserDetails(userId);
      return userDetails;
    });
  }

}

Here, the CompletableFuture.supplyAsync() method is used to execute our service call asynchronously. The result will be wrapped in a CompletableFuture object.

Next, we need to expose this asynchronous operation in our REST controller. We can achieve this by injecting the UserService and using the CompletableFuture returned by fetchUserDetails() method.

@RestController
public class UserController {

  @Autowired
  private UserService userService;

  @GetMapping("/users/{userId}")
  public CompletableFuture<ResponseEntity<UserDetails>> getUserDetails(@PathVariable String userId) {
    return userService.fetchUserDetails(userId)
        .thenApply(ResponseEntity::ok)
        .exceptionally(ex -> ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
  }

}

Here, we utilize the thenApply() method of CompletableFuture to transform the result into a ResponseEntity object. Additionally, we use exceptionally() to handle any exceptions that might occur during the process.

DeferredResult

DeferredResult is another asynchronous programming technique provided by Spring framework that can be used to handle long-running tasks. Unlike CompletableFuture, it has more advanced features like timeouts and cancellations. Let's see how to use it in our Spring Boot application.

Similar to our previous example, we define an asynchronous operation in our service class. Here, we will assume that we are performing a computationally expensive task:

@Service
public class ImageProcessingService {

  public void processImageAsync(String imageUrl, DeferredResult<String> deferredResult) {
    CompletableFuture.supplyAsync(() -> {
      // Simulating expensive image processing
      String result = expensiveImageProcessing(imageUrl);
      return result;
    }).whenComplete((result, throwable) -> {
      if (throwable != null) {
        deferredResult.setErrorResult("An error occurred");
      } else {
        deferredResult.setResult(result);
      }
    });
  }

  private String expensiveImageProcessing(String imageUrl) {
    // Perform the image processing
    return "Processed image URL: " + imageUrl;
  }

}

Here, the DeferredResult object is taken as a parameter and will be completed with the result or error, depending on the outcome of the image processing task.

We can then expose this asynchronous operation in our REST controller by injecting the ImageProcessingService:

@RestController
public class ImageController {

  @Autowired
  private ImageProcessingService imageProcessingService;

  @GetMapping("/process-image")
  public DeferredResult<String> processImage(@RequestParam("imageUrl") String imageUrl) {
    DeferredResult<String> deferredResult = new DeferredResult<>();
    imageProcessingService.processImageAsync(imageUrl, deferredResult);
    return deferredResult;
  }

}

In this example, we create a DeferredResult object and pass it to the processImageAsync() method of the ImageProcessingService. We then return this DeferredResult object as the response.

Conclusion

Using CompletableFuture and DeferredResult can greatly improve the performance and responsiveness of your Spring Boot application. They allow you to handle concurrent requests efficiently and execute long-running tasks asynchronously. By leveraging these techniques, you can create robust and scalable web applications with ease.


noob to master © copyleft