Search code examples
javaspringspring-bootjava-17

How to Implement Asynchronous Processing in a Spring Application for Long-Running Tasks?


How can I implement asynchronous processing in a Java Spring 3-tier application to handle long-running tasks that are expected to take more than 30 seconds, while providing an immediate response to the client after 30 seconds with the message 'Processing in progress. We will notify you when it's completed' Then continue the work and processing in the background. Once the background work is completed, I only need to log the completion without sending any further response to the client.

Note: I work in both JAVA 17 and JAVA 8, so I found most of the functionality didn't work as expected in both of them.

in my controller, I used this :

CompletableFuture.delayedExecutor(30, TimeUnit.SECONDS)

and also like this

Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // return ResponseEntity with the message 'Processing in progress...'
            }
        }, 30000); 

Solution

  • You are making things overly complex. You should be calling an @Async service method, which would return a CompletableFuture (or you can inject the AsyncTaskExecutor and use the submitCompletable to wrap the service call (instead of annotating it with @Async and return a CompletableFuture).

    In the controller use a DeferredResult which will send the result as soon as it is available from the CompletableFuture through the CompletableFuture callback methods. It will also have an onTimeout callback (for 30 seconds) which will fire if the process didn't finish within 30 seconds. It will keep the background process running but will return an answer to the client.

    // Deferred Result with 30 second timeout
    DeferredResult<ResponseEntity<?>> result = new DeferredResult(30_000, () -> ResponseEntity.accepted().body("Processing").build());
    CompletableFuture<YourResult> future = ... // However you achieve this
    future.whenComplete( (res, ex) -> {
      if (res != null) {
        res.setResult(ResponseEntity.ok(res));
      } else {
        res.setErrorResult(ex);
      }
    });
    return result;
    

    Something along those lines. The DeferredResult will wait for 30 seconds, if not return a default response (HTTP 200 ACCEPTED with a message, when the CompletableFuture completes it will return a regular response, or if it was an exception return that).