thenApply/thenCompose and thenApplyAsync/thenCompseAsync


As referenced in the Java migration guide in Play 2.5, with the introduction of Java 8’s CompletionStage/CompletableFuture API, Play’s Promise classes and methods are replaced with the standard Java ones, specifically map becomes thenApplyAsync, and flatMap becomes thenComposeAsync.

When I am reading up on Java 8 (not in context of Play), most of the examples seem to use thenApply and thenCompose (without the Async) a lot more. While I understand the differences between the two for the most part (with and without Async), in context of Play, is it right to just use thenApplyAsync and thenComposeAsync exclusively?

Since the migration doc does not mention the non-Async versions, and the old Promise API does not have a mapAsync or flatMapAsync, it almost seems like the non-Async methods are not “relevant” in context of Play. Are there any cases where the non-Async versions of the methods should be used instead?


@chrono_b I have used the Async versions of these methods. Generally in the controller, if you are chaining your method calls like the following

final CompletableFuture<Collection<A>> someFuture =
someFuture.thenCompose(results -> {
  // .. Transform the results..

In the above case the call to external system was made on a thread-pool I did not have control over. The subsequent thenCompose was executed on this thread-pool.

However down the chain, I had to send response back and it had to be on the executor provided by the play http execution context. So the final call had to be made as follows

final CompletableFuture<Collection<A>> someFuture =
someFuture.thenCompose(results -> {
  // .. Transform the results..
}).thenApplyAsync(someoutput -> {
... More transformations
}, httpExecutionContext.current())

since it is only the async versions of CompletionStage that allow you to specify an executor of your choice.
So one of the usecases I found it was in sending the response on the thread-pool having the right http context.



This is a good and correct explanation. When using the *Async versions, you can both control which thread pool will be used and also use the one that propagates the HTTP context if necessary. But of course, nothing is preventing you from using the non-Async versions.


@aditya and @marcospereira, thank you both for your replies. A follow-up question, if I use the async versions of both methods exclusively (regardless if I am passing in an execution context), would this be considered an “abusive” usage? Would there be any penalties?

@chrono_b I think the Async flavours are a little misleading, and really no more async than the non-async ones. Just that they allow you to choose a thread-pool which the non-async ones do not give you some flexibility by choosing dedicated thread pools (with own tuning) to perform a task in every stage.

If you do not choose to specify a thread-pool, you are relying on the default fork join pool to exe
cute which is not very different from what the non-async versions do.

So to answer your query: Surely not abusive to use async flavours, but they will be more beneficial IMO with the appropriate execution context as opposed to relying on the default jvm fork join pool which I dont think can be tuned.

1 Like