Search code examples
javaspring-bootspring-async

Executing and handling Void @Async operations from Spring Boot resources


Java 8 and Spring Boot 2.x here. I have a RESTful resource that will kick off a long-running operation, potentially taking 15 - 20 minutes to complete in some cases. I think I want to leverage the @Async annotation here in the service-layer, but I'm open to any good, elegant Spring Boot-savvy solution!

My best attempt thus far:

@RestController
@RequestMapping(path = "/v1/fizzbuzzes")
public class FizzbuzzResource {

    @Autowired
    private FizzbuzzService fizzbuzzService;

    @Autowired
    private FizzbuzzRepository fizzbuzzRepository;

    @Autowired
    @Qualifier("fizzbuzz.ids")
    private List<String> fizzbuzzList;

    @PostMapping("/{fizzbuzzId}")
    public ResponseEntity<Void> runFizzbuzzOperations(@PathVariable String fizzbuzzId)
            throws ExecutionException, InterruptedException {

        ResponseEntity responseEntity;

        // verify the fizzbuzzId is valid -- if it is, we process it
        Optional<Fizzbuzz> fbOpt = fizzbuzzRepository.lookupMorph(fizzbuzzId);
        if (fbOpt.isPresent()) {

            fizzbuzzList.add(fizzbuzzId);

            CompletableFuture<Void> future = fizzbuzzService.runAsync(fbOpt.get());
            future.get();
            // TODO: need help here

            // TODO: decrement the list once the async has completed -- ONLY do once async has finished
            fizzbuzzList.remove(fizzbuzzId);

            // return success immediately (dont wait for the async processing)
            responseEntity = ResponseEntity.ok().build();

        } else {
            responseEntity = ResponseEntity.notFound().build();
        }

        return responseEntity;

    }

}

@Service
public class FizzbuzzService {

    @Async
    public CompletableFuture<Void> runAsync(Fizzbuzz fizzbuzz) {

        // do something that can take 15 - 20 mins to complete
        // it actually is writing a massive amount of data to the file system
        // so there's nothing to really "return" so we just return null (?)

        return CompletableFuture.completedFuture(null);

    }

}

I think I'm close, but I'm struggling with:

  • How to properly invoke fizzbuzzService.runAsync(...) in such a way that it actually runs asynchronously, and so that the ResponseEntity.ok().build() underneath it runs immediately, instead of waiting the ~15 minutes like it would have to otherwise; and
  • How to run fizzbuzzList.remove(...) as soon as the async service method completes (again, some 15 - 20 mins later), but no sooner!; and
  • Perhaps configure some type of timeout on the async operation, failing with an exception after, say, 30 minutes

Can anyone spot where I'm going awry?


Solution

    1. Remove call to CompletableFuture.get(). It waits for future to complete.
    2. Pass consumer to thenAccept. You can find examples here.
    3. For time outs, in Java 9, check orTimeOut. Check this for example. If you check the article in previous link, in Java 8, there is no clean way of handling timeouts. You can set default timeout for entire application by providing custom async executor. Check this stackoverflow question for solution.