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 through a WSClient instance.

There are two important parts to using the WSClient: 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 WSClient. 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.

You end by calling a method corresponding to the HTTP method you want to use. This ends the chain, and uses all the options defined on the built request in the WSRequest.

val futureResponse: Future[WSResponse] = complexRequest.get()

This returns a Future[WSResponse] where the Response contains the data returned from the server.

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.

If you need to use HTTP authentication, you can specify it in the builder, using a username, password, and an AuthScheme. Valid case objects for the AuthScheme are BASIC, DIGEST, KERBEROS, NTLM, and SPNEGO.

It’s also possible to stream data in the request body using Akka Streams.

For example, imagine you have executed a database query that is returning a large image, and you would like to forward that data to a different endpoint for further processing. Ideally, if you can send the data as you receive it from the database, you will reduce latency and also avoid problems resulting from loading in memory a large set of data. If your database access library supports Reactive Streams (for instance, Slick does), here is an example showing how you could implement the described behavior:

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

A sample request filter that logs the request in cURL format to SLF4J has been added in play.api.libs.ws.ahc.AhcCurlRequestLogger.

Working with the Response is easily done by mapping inside the Future.

The examples given below have some common dependencies that will be shown once here for brevity.

Whenever an operation is done on a Future, an implicit execution context must be available - this declares which thread pool the callback to the future should run in. You can inject the default Play execution context in your DI-ed class by declaring an additional dependency to ExecutionContext in the class’ constructor:

class PersonService @Inject()(ec: ExecutionContext) {
// ...
}

The examples also use the following case class for serialization/deserialization:

case class Person(name: String, age: Int)

The WSResponse extends play.api.libs.ws.WSBodyReadables trait, which contains type classes for Play JSON and Scala XML conversion. You can also create your own custom type classes if you would like to convert the response to your own types, or use a different JSON or XML encoding.

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.

WS lets you consume the response’s body incrementally by using an Akka StreamsSink. The stream() method on WSRequest returns a streaming WSResponse which contains a bodyAsSource method that returns 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.

Here is a trivial example that uses a folding Sink to count the number of bytes returned by the response:

When making a request from a controller, you can map the response to a Future[Result]. This can be used in combination with Play’s Action.async action builder, as described in Handling Asynchronous Results.

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 play.api.libs.concurrent.Futures:

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.

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.

You need an instance of an akka.stream.Materializer to create a play.api.libs.ws.ahc.AhcWSClient instance directly. Usually you’ll inject this into the service using dependency injection:

Again, once you are done with your custom client work, you must close the client:

wsClient.close()

Ideally, you should close a client after you know all requests have been completed. Be careful of using an automatic resource management pattern to close the client, because WSClient logic is asynchronous and many ARM solutions may be designed for a single threaded synchronous solution.

If you want to call WS outside of the context 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:

Play WS comes with rich type support for bodies in the form of play.api.libs.ws.WSBodyWritables, which contains type classes for converting input such as JsValue or XML in the body of a WSRequest into a ByteString or Source[ByteString, _], and play.api.libs.ws.WSBodyReadables, which aggregates type classes that read the body of a WSResponse from a ByteString or Source[ByteString, _] and return the appropriate type, such as JsValue or XML. These type classes are automatically in scope when you import the ws package, but you can also create custom types. This is especially useful if you want to use a custom library, i.e. you would like to stream XML through STaX API or use another JSON library such as Argonaut or Circe.