scalaplayframeworkscalatestplayframework-2.5

Play 2.5 preserve context in async calls


In our controller class we reach out to another service to get some data :

Future<JsonNode> futureSite = someClient.getSite(siteId, queryParams);

return FutureConverters.toJava(futureSite).thenApplyAsync((siteJson) -> {
    Site site = Json.fromJson(siteJson, Site.class);
    try {
        return function.apply(site);
    } catch (RequestException e) {
        return e.result;
    }
}).exceptionally(throwable -> {
    if(throwable instanceof OurClientException) {
        if(((OurClientException) throwable).httpStatusCode == 404) {
           return entityNotFound("Site", siteId);
        }
    }
    return null;
});

What we notice is that context which is set in unit tests (we use scalatest-play) is lost and becomes null after we make the Async call (FutureConverters.toJava(futureSite).thenApplyAsync((siteJson), as t is on a separate thread.

Which causes problem down in the controller code, where we use the above function ... request() would now throw a runtime exception saying there is no context available.

How can we preserve the context ?


Solution

  • You should inject play.libs.concurrent.HttpExecutionContext to your controller and then specify current context as second argument for CompletionStage#thenApplyAsync(..,..).

    public class Application extends Controller {
    @Inject HttpExecutionContext ec;
    
    public CompletionStage<Result> index() {
        someCompletableFuture.supplyAsync(() -> { 
          // do something with request()
        }, ec.current());
    }}
    

    P.S. https://www.playframework.com/documentation/2.5.x/JavaAsync#Using-CompletionStage-inside-an-Action