Sometimes we would like to call other HTTP services from within a Play application. Play supports this via its WS library, which provides a way to make asynchronous HTTP calls.

There are two important parts to using the WS API: making a request, and processing the response. We’ll discuss how to make both GET and POST HTTP requests first, and then show how to process the response from the WS library. Finally, we’ll discuss some common use cases.

Note: In Play 2.6, Play WS has been split into two, with an underlying standalone client that does not depend on Play, and a wrapper on top that uses Play specific classes. In addition, shaded versions of AsyncHttpClient and Netty are now used in Play WS to minimize library conflicts, primarily so that Play’s HTTP engine can use a different version of Netty. Please see the 2.6 migration guide for more information.

If you are doing any blocking work, including any kind of DNS work such as calling java.util.URL.equals(), then you should use a custom execution context as described in ThreadPools, preferably through a CustomExecutionContext. You should size the pool to leave a safety margin large enough to account for failures.

You can do additional processing on a WSRequest by adding a request filter. A request filter is added by extending the play.libs.ws.WSRequestFilter interface, and then adding it to the request with request.setRequestFilter(filter).

Calling get(), post() or execute() will cause the body of the response to be loaded into memory before the response is made available. When you are downloading a large, multi-gigabyte file, this may result in unwelcome garbage collection or even out of memory errors.

You can consume the response’s body incrementally by using an Akka StreamsSink. The stream() method on WSRequest returns a CompletionStage<WSResponse>, where the WSResponse contains a getBodyAsStream() method that provides a Source<ByteString, ?>.

Note: In 2.5.x, a StreamedResponse was returned in response to a request.stream() call. In 2.6.x, a standard WSResponse is returned, and the getBodyAsSource() method should be used to return the Source.

Any controller or component that wants to leverage the WS streaming functionality will have to add the following imports and dependencies:

If a chain of WS calls does not complete in time, it may be useful to wrap the result in a timeout block, which will return a failed Future if the chain does not complete in time – this is more generic than using withRequestTimeout, which only applies to a single request.The best way to do this is with Play’s non-blocking timeout feature, using Futures.timeout and CustomExecutionContext to ensure some kind of resolution:

We recommend that you get your WSClient instances using dependency injection as described above. WSClient instances created through dependency injection are simpler to use because they are automatically created when the application starts and cleaned up when the application stops.

However, if you choose, you can instantiate a WSClient directly from code and use this for making requests or for configuring underlying AsyncHttpClient options.

Note: If you create a WSClient manually then you must call client.close() to clean it up when you’ve finished with it. Each client creates its own thread pool. If you fail to close the client or if you create too many clients then you will run out of threads or file handles -— you’ll get errors like “Unable to create new native thread” or “too many open files” as the underlying resources are consumed.

Ideally, you should only close a client after you know all requests have been completed. You should not use try-with-resources to automatically close a WSClient instance, because WSClient logic is asynchronous and try-with-resources only supports synchronous code in its body.

Play WS comes with rich type support for bodies in the form of play.libs.ws.WSBodyWritables, which contains methods for converting input such as JsonNode or XML in the body of a WSRequest into a ByteString or Source<ByteString, ?>, and play.libs.ws.WSBodyReadables, which contains methods that read the body of a WSResponse from a ByteString or Source[ByteString, _] and return the appropriate type, such as JsValue or XML. The default methods are available to you through the WSRequest and WSResponse, but you can also use custom types with response.getBody(myReadable()) and request.post(myWritable(data)). This is especially useful if you want to use a custom library, i.e. you would like to stream XML through STaX API.

If you want to call WS outside of Play altogether, you can use the standalone version of Play WS, which does not depend on any Play libraries. You can do this by adding play-ahc-ws-standalone to your project: