Documentation

You are viewing the documentation for the 2.6.19 release in the 2.6.x series of releases. The latest stable release series is 3.0.x.

§The Play cache API

Caching data is a typical optimization in modern applications, and so Play provides a global cache.

An important point about the cache is that it behaves just like a cache should: the data you just stored may just go missing.

For any data stored in the cache, a regeneration strategy needs to be put in place in case the data goes missing. This philosophy is one of the fundamentals behind Play, and is different from Java EE, where the session is expected to retain values throughout its lifetime.

The default implementation of the Cache API uses Ehcache.

§Importing the Cache API

Play provides both an API and an default Ehcache implementation of that API. To get the full Ehcache implementation, add ehcache to your dependencies list:

libraryDependencies ++= Seq(
  ehcache
)

This will also automatically set up the bindings for runtime DI so the components are injectable. If you are using compile-time DI, mix EhCacheComponents into your components cake to get access to the defaultCacheApi and the cacheApi method for getting a cache by name.

To add only the API, add cacheApi to your dependencies list.

libraryDependencies ++= Seq(
  cacheApi
)

The API dependency is useful if you’d like to define your own bindings for the Cached helper and AsyncCacheApi, etc., without having to depend on Ehcache. If you’re writing a custom cache module you should use this.

§JCache Support

Ehcache implements the JSR 107 specification, also known as JCache, but Play does not bind javax.caching.CacheManager by default. To bind javax.caching.CacheManager to the default provider, add the following to your dependencies list:

libraryDependencies += jcache

If you are using Guice, you can add the following for Java annotations:

libraryDependencies += "org.jsr107.ri" % "cache-annotations-ri-guice" % "1.0.0"

§Accessing the Cache API

The cache API is defined by the AsyncCacheApi and SyncCacheApi traits, depending on whether you want an asynchronous or synchronous implementation, and can be injected into your component like any other dependency. For example:

import play.api.cache._
import play.api.mvc._
import javax.inject.Inject

class Application @Inject() (cache: AsyncCacheApi, cc:ControllerComponents) extends AbstractController(cc) {

}

Note: The API is intentionally minimal to allow several implementation to be plugged in. If you need a more specific API, use the one provided by your Cache plugin.

Using this simple API you can store data in cache:

val result: Future[Done] = cache.set("item.key", connectedUser)

And then retrieve it later:

val futureMaybeUser: Future[Option[User]] = cache.get[User]("item.key")

There is also a convenient helper to retrieve from cache or set the value in cache if it was missing:

val futureUser: Future[User] = cache.getOrElseUpdate[User]("item.key") {
  User.findById(connectedUser)
}

Note: getOrElseUpdate is not an atomic operation in Ehcache and is implemented as a get followed by computing the value, then a set. This means it’s possible for the value to be computed multiple times if multiple threads are calling getOrElse simultaneously.

You can specify an expiry duration by passing a duration, by default the duration is infinite:

import scala.concurrent.duration._

val result: Future[Done] = cache.set("item.key", connectedUser, 5.minutes)

To remove an item from the cache use the remove method:

val removeResult: Future[Done] = cache.remove("item.key")

To remove all items from the cache use the removeAll method:

val removeAllResult: Future[Done] = cache.removeAll()

removeAll() is only available on AsyncCacheApi, since removing all elements of the cache is rarely something you want to do sychronously. The expectation is that removing all items from the cache should only be needed as an admin operation in special cases, not part of the normal operation of your app.

Note that the SyncCacheApi has the same API, except it returns the values directly instead of using futures.

§Accessing different caches

It is possible to access different caches. In the default Ehcache implementation, the default cache is called play, and can be configured by creating a file called ehcache.xml. Additional caches may be configured with different configurations, or even implementations.

If you want to access multiple different ehcache caches, then you’ll need to tell Play to bind them in application.conf, like so:

play.cache.bindCaches = ["db-cache", "user-cache", "session-cache"]

By default, Play will try to create these caches for you. If you would like to define them yourself in ehcache.xml, you can set:

play.cache.createBoundCaches = false

Now to access these different caches, when you inject them, use the NamedCache qualifier on your dependency, for example:

import play.api.cache._
import play.api.mvc._
import javax.inject.Inject

class Application @Inject()(
    @NamedCache("session-cache") sessionCache: AsyncCacheApi,
    cc: ControllerComponents
) extends AbstractController(cc) {

}

§Setting the execution context

By default, all Ehcache operations are blocking, and async implementations will block threads in the default execution context. Usually this is okay if you are using Play’s default configuration, which only stores elements in memory since reads should be relatively fast. However, depending on how EhCache was configured and where the data is stored, this blocking I/O might be too costly. For such a case you can configure a different Akka dispatcher and set it via play.cache.dispatcher so the EhCache plugin makes use of it:

play.cache.dispatcher = "contexts.blockingCacheDispatcher"

contexts {
  blockingCacheDispatcher {
    fork-join-executor {
      parallelism-factor = 3.0
    }
  }
}

§Caching HTTP responses

You can easily create smart cached actions using standard Action composition.

Note: Play HTTP Result instances are safe to cache and reuse later.

The Cached class helps you build cached actions.

import play.api.cache.Cached
import javax.inject.Inject

class Application @Inject() (cached: Cached, cc:ControllerComponents) extends AbstractController(cc) {

}

You can cache the result of an action using a fixed key like "homePage".

def index = cached("homePage") {
  Action {
    Ok("Hello world")
  }
}

If results vary, you can cache each result using a different key. In this example, each user has a different cached result.

def userProfile = WithAuthentication(_.session.get("username")) { userId =>
  cached(req => "profile." + userId) {
    Action.async {
      User.find(userId).map { user =>
        Ok(views.html.profile(user))
      }
    }
  }
}

§Control caching

You can easily control what you want to cache or what you want to exclude from the cache.

You may want to only cache 200 Ok results.

def get(index: Int) = cached.status(_ => "/resource/"+ index, 200) {
  Action {
    if (index > 0) {
      Ok(Json.obj("id" -> index))
    } else {
      NotFound
    }
  }
}

Or cache 404 Not Found only for a couple of minutes

def get(index: Int) = {
  val caching = cached
    .status(_ => "/resource/"+ index, 200)
    .includeStatus(404, 600)

  caching {
    Action {
      if (index % 2 == 1) {
        Ok(Json.obj("id" -> index))
      } else {
        NotFound
      }
    }
  }
}

§Custom implementations

It is possible to provide a custom implementation of the cache API that either replaces, or sits along side the default implementation.

To replace the default implementation based on something other than Ehcache, you only need the cacheApi dependency rather than the ehcache dependency in your build.sbt. If you still need access to the Ehcache implementation classes, you can use ehcache and disable the module from automatically binding it in application.conf:

play.modules.disabled += "play.api.cache.ehcache.EhCacheModule"

You can then implement AsyncCacheApi and bind it in the DI container. You can also bind SyncCacheApi to DefaultSyncCacheApi, which simply wraps the async implementation.

Note that the removeAll method may not be supported by your cache implementation, either because it is not possible or because it would be unnecessarily inefficient. If that is the case, you can throw an UnsupportedOperationException in the removeAll method.

To provide an implementation of the cache API in addition to the default implementation, you can either create a custom qualifier, or reuse the NamedCache qualifier to bind the implementation.

Next: Calling REST APIs with Play WS