Documentation

You are viewing the documentation for the 2.4.0-M2 development release. The latest stable release series is 3.0.x.

§Play 2.4 Migration Guide

This is a guide for migrating from Play 2.3 to Play 2.4. If you need to migrate from an earlier version of Play then you must first follow the Play 2.3 Migration Guide.

§Dependency Injection

Play now, out of the box, uses dependency injection provided by Guice. This is part of a long term strategy to remove global state out of Play, which we hope to complete in the Play 3.0 release. Moving any application from depending on global state to being entirely global state free is a big task, one that can be very disruptive if it is done all at once. For this reason, the approach we’ve taken in Play is to spread the change over a number of releases, allowing end users to gradually migrate their code so that it doesn’t depend on global state, rather than forcing it all at once.

As much as practical, we have ensured that the APIs provided in Play 2.4 are source compatible with Play 2.3. This means, in many situations, there are two ways of doing things, a way that depends on global state, and a way that doesn’t. We’ve updated the documentation to reflect the new dependency injection approach of doing things - in cases where you still want to use the old APIs and see documentation about them, in general, the Play 2.3 documentation is still relevant.

It’s important that you read the documentation about dependency injection in Play before proceeding with migrating to Play 2.4. There are some decisions to make up front. Out of the box we provide and encourage the use of Guice for dependency injection, but many other dependency injection tools and techniques, including compile time dependency injection techniques in Scala are possible. You can read about dependency injection in Java or Scala.

§Routing

One of the most disruptive changes with regards to dependency injection is we now support the generation of two styles of routers. The first is the existing static style, this is largely unchanged from the Play 2.3 router. It is a Scala singleton object, and assumes that all the actions that it invokes are either Scala singleton objects, or Java static methods. The second is a dependency injected router, which is a class that declares its dependencies in its constructor. To illustrate the difference between these two routers, consider the following routes file:

GET   /               controllers.Application.index
POST  /save           controllers.Application.save
GET   /assets/*file   controllers.Assets.versioned(path = "/public", file: Asset)

The static routes generator will generate a router that very roughly (pseudo code) looks like this:

object Routes extends Router.Routes {
  def routes = {
    case ("GET", "/") => controllers.Application.index
    case ("POST", "/save") => controllers.Application.save
    case ("GET", "/assets/:file") => controllers.Assets.versioned("/public", file)
  }
}

Meanwhile the injected routes generator will generate a router that very roughly looks like this:

class Routes(application: controllers.Application, assets: controllers.Assets) extends Router.Routes {
  def routes = {
    case ("GET", "/") => application.index
    case ("POST", "/save") => application.save
    case ("GET", "/assets/:file") => assets.versioned("/public", file)
  }
}

The default is to use the static routes generator. You must use this if you are not ready to migrate all of your Java actions to be non static methods, or your Scala actions to to be classes. In most cases, this is quite straight forward to do, in Java it requires deleting the static keyword, in Scala it requires changing the word object to class. The static router still supports the @ operator, which will tell it to look up the action from a runtime Injector, you may find this useful if you are in a transitional period where some of your actions are static and some are injected.

If you wish to switch to the injected generator, add the following to your build settings in build.sbt:

routesGenerator := InjectedRoutesGenerator

By default Play will automatically handle the wiring of this router for you using Guice, but depending in the DI approach you’re taking, you may be able to customise it.

The injected routes generator also supports the @ operator on routes, but it has a slightly different meaning (since everything is injected), if you prefix a controller with @, instead of that controller being directly injected, a JSR 330 Provider for that controller will be injected. This can be used, for example, to eliminate circular dependency issues, or if you want a new action instantiated per request.

§Dependency Injected Components

While Play 2.4 won’t force you to use the dependency injected versions of components, we do encourage you to start switching to them. The following tables show old static APIs that use global state and new injected APIs that you should be switching to:

§Scala

Old API New API Comments
Lang Langs
Messages MessagesApi Using one of the preferred methods, you can get a Messages instance.
DB DBApi or better, Database You can get a particular database using the @NamedDatabase annotation.
Cache CacheApi or better You can get a particular cache using the @NamedCache annotation.
Cached object Cached instance Use an injected instance instead of the companion object. You can use the @NamedCache annotation.
Akka N/A No longer needed, just declare a dependency on ActorSystem
WS WSClient

§Java

Old API New API Comments
Lang Langs Instances of Lang objects are still fine to use
Messages MessagesApi Using one of the preferred methods, you can get a Messages instance, and you can then use at to get messages for that lang.
DB DBApi or better, Database You can get a particular database using the @NamedDatabase annotation.
JPA JPAApi
Cache CacheApi You can get a particular cache using the @NamedCache annotation.
Akka N/A No longer needed, just declare a dependency on ActorSystem
WS WSClient

§Body Parsers

The default body parser is now play.api.mvc.BodyParsers.parse.default. It is similar to anyContent parser, except that it only parses the bodies of PATCH, POST, and PUT requests. To parse bodies for requests of other methods, explicitly pass the anyContent parser to Action.

def foo = Action(play.api.mvc.BodyParsers.parse.anyContent) { request =>
  Ok(request.body.asText)
}

§Maximum body length

For both Scala and Java, there have been some small but important changes to the way the configured maximum body lengths are handled and applied.

A new property, parsers.disk.maxLength, specifies the maximum length of any body that is parsed by a parser that may buffer to disk. This includes the raw body parser and the multipart/form-data parser. By default this is 10MB.

In the case of the multipart/form-data parser, the aggregate length of all of the text data parts is limited by the configured parsers.text.maxLength value, which defaults to 100KB.

In all cases, when one of the max length parsing properties is exceeded, a 413 response is returned. This includes Java actions who have explicitly overridden the maxLength property on the BodyParser.Of annotation - previously it was up to the Java action to check the RequestBody.isMaxSizeExceeded flag if a custom max length was configured, this flag has now been deprecated.

Additionally, Java actions may now declare a BodyParser.Of.maxLength value that is greater than the configured max length.

§Java TimeoutExceptions

If you use the Java API, the F.Promise class now throws unchecked F.PromiseTimeoutExceptions instead of Java’s checked TimeoutExceptions. The TimeoutExceptionss which were previously used were not properly declared with the throws keyword. Rather than changing the API to use the throws keyword, which would mean users would have to declare throws on their methods, the exception was changed to a new unchecked type instead. See #1227 for more information.

Old API New API Comments
TimeoutException F.PromiseTimeoutException

§Crypto APIs

Play’s Crypto API improves security by supporting encryption methods that use initialization vectors and by changing the default encryption transformation to AES/CTR/NoPadding. To add this support, the Play 2.4 encryption format has changed slightly. This means that cookies and other data encrypted in Play 2.4 will not be readable by older versions of Play. However, Play 2.4 can read cookies and other data encrypted in both the old and new format.

The crypto transformation is configured in play.crypto.aes.transformation and the default value has changed from AES to AES/CTR/NoPadding, which is more secure.

When you call Crypto.encryptAES in Play 2.4 it will use the configured transformation (default AES/CTR/NoPadding) to encrypt the data and then encode the result in the new format that supports initialization vectors.

When you call Crypto.decryptAES it will decode both the old and new formats. The old format is always decoded using the AES transformation. The new format is decoded using the configured transformation (default AES/CTR/NoPadding).

If you wish to continue using the older format of encryption decryption, here is the link that provides all the necessary information.

§Anorm

New Anorm version includes various fixes and improvements.

Following BatchSQL #3016, SqlQuery case class is refactored as a trait with companion object.
Consequently, BatchSql is now created by passed a raw statement which is validated internally.

import anorm.BatchSql

// Before
BatchSql(SqlQuery("SQL")) // No longer accepted (won't compile)

// Now
BatchSql("SQL")
// Simpler and safer, as SqlQuery is created&validated internally

§Parsing

It’s now possible to get value from Row using column index.

val res: (String, String) = SQL("SELECT * FROM Test").map(row =>
 row[String](1) -> row[String](2) // string columns #1 and #2
)

Column resolution per label is now unified, whatever the label is name or alias.

val res: (String, Int) = SQL"SELECT text, count AS i".map(row =>
  row[String]("text") -> row[Int]("i")
)

New fold and foldWhile functions to work with result stream.

val countryCount: Either[List[Throwable], Long] = 
  SQL"Select count(*) as c from Country".fold(0l) { (c, _) => c + 1 }

val books: Either[List[Throwable], List[String]] =
 SQL("Select name from Books").foldWhile(List[String]()) { (list, row) => 
  foldWhile(List[String]()) { (list, row) =>
    if (list.size == 100) (list -> false) // stop with `list`
    else (list := row[String]("name")) -> true // continue with one more name
  }

New withResult function to provide custom stream parser.

import anorm.{ Cursor, Row }
@annotation.tailrec
def go(c: Option[Cursor], l: List[String]): List[String] = c match {
  case Some(cursor) => {
    if (l.size == 100) l // custom limit, partial processing
    else {
      val row = it.next()
      go(it, l :+ row[String]("name"))
    }
  }
  case _ => l
}

val books: Either[List[Throwable], List[String]] =
  SQL("Select name from Books").withResult(go(_, List.empty[String]))

§Type mappings

More parameter and column conversions are available.

Array

A column can be multi-value if its type is JDBC array (java.sql.Array). Now Anorm can map it to either array or list (Array[T] or List[T]), provided type of element (T) is also supported in column mapping.

import anorm.SQL
import anorm.SqlParser.{ scalar, * }

// array and element parser
import anorm.Column.{ columnToArray, stringToArray }

val res: List[Array[String]] =
  SQL("SELECT str_arr FROM tbl").as(scalar[Array[String]].*)

New convenient parsing functions are also provided for arrays with SqlParser.array[T](...) and SqlParser.list[T](...)

In case JDBC statement is expecting an array parameter (java.sql.Array), its value can be passed as Array[T], as long as element type T is a supported one.

val arr = Array("fr", "en", "ja")
SQL"UPDATE Test SET langs = $arr".execute()

Multi-value parameter

New conversions are available to pass List[T], Set[T], SortedSet[T], Stream[T] and Vector[T] as multi-value parameter.

SQL("SELECT * FROM Test WHERE cat IN ({categories})").
 on('categories -> List(1, 3, 4)

SQL("SELECT * FROM Test WHERE cat IN ({categories})").
 on('categories -> Set(1, 3, 4)

SQL("SELECT * FROM Test WHERE cat IN ({categories})").
 on('categories -> SortedSet("a", "b", "c")

SQL("SELECT * FROM Test WHERE cat IN ({categories})").
 on('categories -> Stream(1, 3, 4)

SQL("SELECT * FROM Test WHERE cat IN ({categories})").
 on('categories -> Vector("a", "b", "c")

Numeric and boolean types

Column conversions for basic types like numeric and boolean ones have been improvided.

Some invalid conversions are removed:

Column (JDBC type) (as) JVM/Scala type
Double Boolean
Int Boolean

There are new conversions extending column support.

Column (JDBC type) (as) JVM/Scala type
BigDecimal BigInteger
BigDecimal Int
BigDecimal Long
BigInteger BigDecimal
BigInteger Int
BigInteger Long
Boolean Int
Boolean Long
Boolean Short
Byte BigDecimal
Float BigDecimal
Int BigDecimal
Long Int
Short BigDecimal

Misc

Next: Play 2.3 migration guide