The support for Java 6 and Java 7 was dropped and Play 2.4 now requires Java 8. This decision was made based on the fact that Java 7 reached its End-of-Life in April 2015. Also, Java 8 enables clean APIs and has better support for functional programming style. If you try to use Play 2.4 with Java 6/7, you will get an error like below:

Note: Scala 2.10 does not have full support to all Java 8 language features, like static methods on interfaces. If your project has Java code using these new features present in Java 8, upgrade to use Scala 2.11.6+. See sbt docs to learn how to set scalaVersion to your project.

Support for database evolutions used to be included with both Play JDBC and JPA support. That’s no longer the case. Therefore, if you are using evolutions, you now need to add an explicit dependency to evolutions in your project’s build:

libraryDependencies += evolutions

While, if you are not using evolutions, you can now safely remove evolutionplugin=disabled from your application.conf.

The configuration keys have changed, so instead of evolutionplugin=disabled you now need to use play.evolutions.enabled=false (See Evolutions configuration)

If you are using Play Slick module (with or without evolutions), things have changed quite a bit, so make sure to read the Play Slick migration guide.

All classes in the SBT plugin are now in the package play.sbt, this is particularly pertinent if using .scala files to configure your build. You will need to import identifiers from play.sbt.PlayImport to use play provided configuration elements.

Ebean has been pulled out into an external project, to allow it to have a lifecycle independent of Play’s own lifecycle. The Ebean bytecode enhancement functionality has also been extracted out of the Play sbt plugin into its own plugin.

To migrate an existing Play project that uses Ebean to use the new external Ebean plugin, remove javaEbean from your libraryDependencies in build.sbt, and add the following to project/plugins.sbt:

Additionally, Ebean has been upgraded to 4.5.x, which pulls in a few of the features that Play previously added itself, including the Model class. Consequently, the Play Model class has been deprecated, in favour of using com.avaje.ebean.Model.

Play's bytecode enhancement, which generates getters and setters for Java properties, has been pulled out of the core of Play into a separately managed project that can have its own lifecycle. To enable it, add the following to your project/plugins.sbt file:

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.

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:

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 be classes. In most cases, this is quite straightforward 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.

In addition, Play now, by default, generates the router in the router package, instead of at the root package. This is to aid with dependency injection, so if needed it can be manually created or bound, since classes in the root package can’t usually be referenced.

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:

If you are keen to use dependency injection, we are recommending that you move out of your GlobalSettings implementation class as much code as possible. Read the `GlobalSettings` migration documentation for the gory details.

Play 2.4 now uses reference.conf to document and specify defaults for all properties. You can easily find these by going here and searching for files called reference.conf.

Additionally, Play has now better namespaced a large number of its configuration properties. The old configuration paths will generally still work, but a deprecation warning will be output at runtime if you use them. Here is a summary of the changed keys:

Play 2.4 now has just one actor system. Before, the internal actor system was configured under play.akka and the Akka plugin was configured under akka. The new combined actor system is configured under akka. There is no actor system configuration under play.akka anymore. However, several Play specific settings are still given under the play.akka prefix.

If you want to change how the actor system is configured, you can set play.akka.config = "my-akka", where my-akka is your chosen configuration prefix.

Previously the two actor systems had slightly different thread pool configuration. Now that there is only one actor system, the configuration has been merged. We’ve also added a LIFO (stack-based) scheduling rule which should improve performance in most Play applications.

The following settings are the new defaults in Play 2.4. They’ve been shown to have good performance in our testing, but every application is different so you may need to tweak them or revert them to the Play 2.3 settings. You can do that by overriding any of these values in your application.conf. Here are the new settings:

The HttpRequestHandler that Play uses by default delegates to the legacy GlobalSettings methods. If you’re not using GlobalSettings in your application then you can increase performance slightly by changing the handler. You can do that by adding the following to your settings:

This occurs if your JDBC-Drivers do not support Connection.isValid(). The fastest and recommended fix is to make sure you use the latest version of your JDBC-Driver. If upgrading to the latest version does not help, you may specify the connectionTestQuery in your application.conf like this

#specify a connectionTestQuery. Only do this if upgrading the JDBC-Driver does not help
db.default.hikaricp.connectionTestQuery="SELECT TRUE"

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.

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, play.http.parser.maxDiskBuffer, 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 play.http.parser.maxMemoryBuffer 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.

The semantics of JSON lookups have changed slightly. JsUndefined has been removed from the JsValue type hierarchy and all lookups of the form jsv \ foo or jsv(bar) have been moved to JsLookup. They now return a JsLookupResult instead of a JsValue.

If you have code of the form

val v: JsValue = json \ "foo" \ "bar"

the following code is equivalent, if you know the property exists:

val v: JsValue = (json \ "foo" \ "bar").get

If you don’t know the property exists, we recommend using pattern matching or the methods on JsLookupResult to safely handle the JsUndefined case, e.g.

All JSON traversal methods have been moved to the JsLookup class, which is implicitly applied to all values of type JsValue or JsLookupResult. In addition to the apply, \, and \\ methods, the head, tail, and last methods have been added for JSON arrays. All methods except \\ return a JsLookupResult, a wrapper for JsValue that helps with handling undefined values.

The methods as[A], asOpt[A], validate[A] also exist on JsLookup, so code like the below should require no source changes:

OptionReads is no longer available by default in 2.4. If you have code of the form jsv.validate[Option[A]], you’ll need to rewrite it in one of the following ways:

To map both JsNull and an undefined lookup result to None, use jsv.validateOpt[A]. This is typically what you want.

To map all validation errors to None, use JsSuccess(jsv.asOpt[A]). This was what reading an Option did in 2.3.

To map JsNull to None and validate the value if it exists, use jsv.validate(Reads.optionWithNull[A]). If the value does not exist the result will be a JsError.

When using the functional combinators to construct your Reads, you will most likely want to replace

(JsPath \ "property").read[Option[String]]

with

(JsPath \ "property").readNullable[String]

This does the same as (1) above. The same goes for Writes with JsPath.writeNullable and Format with JsPath.formatNullable. Note that readNullable will return a JsError if a node could not be found before the last node, instead of JsSuccess(None).

Finally, you may run into problems creating a serializer for a type that requires a Reads for Option such as Seq[Option[String]]. In this case, you just need to create a reads for Option[String] and make sure it is in scope:

The reverse ref router used in Java tests has been removed. Any call to Helpers.call that was passed a ref router can be replaced by a call to Helpers.route which takes either a standard reverse router reference or a RequestBuilder.

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.

WSRequestHolder has been renamed to WSRequest in Scala and Java. The previous WSRequest class has been removed out as it was only used internally to WS for OAuth functionality.

WS has upgraded from AsyncHttpClient 1.8.x to 1.9.x, which includes a number of breaking changes if using or configuring that library directly. Please see the AsyncHttpClient Migration Guide for more details. The upgrade to AsyncHttpClient 1.9.x enables Server Name Indication (SNI) in HTTPS – this solves a number of problems with HTTPS based CDNs such as Cloudflare which depend heavily on SNI.

Configuration settings for WS have changed:

ws.acceptAnyCertificate has been moved under the loose settings as play.ws.ssl.loose.acceptAnyCertificate to better indicate the insecure nature of blindly accepting any X.509 certificate without validation.

ws.ssl.debug settings have been redefined as booleans, e.g. play.ws.ssl.debug.all=true. Please see Debugging SSL for details.

ws.ssl.disabledSignatureAlgorithms and ws.ssl.disabledKeyAlgorithms have been redefined as arrays of strings, e.g play.ws.ssl.disabledSignatureAlgorithms = ["MD2", "MD4", "MD5"].

Because of the AsyncHttpClient 1.9.x upgrade, several settings no longer have the same names that they did previously in the 1.8.x version AsyncHttpClientConfig.Builder. To reduce confusion, here is the map from WS settings to 1.9.x AsyncHttpClientConfig.Builder:

WS has changed the OAuth signature calculator from Signpost to AsyncHttpClient’s OAuthCalculator. Signpost is still used to retrieve the request token and access tokens. This should not require any application level changes, but is worth noting in case of unexpected OAuth failures.

Due to the recent spate of TLS vulnerabilities, there has been more activity to deprecate insecure HTTPS configurations. Per RFC 7465, RC4 cipher suites have added to the list of deprecated ciphers, and are not available by default. They may be explicitly enabled as cipher suites using the play.ws.ssl.enabledCiphers and play.ws.ssl.loose.allowWeakCiphers settings. Please also consider reviewing RFC 7525 for the IETF recommended configuration of TLS.

Play 2.4’s AES encryption now uses initialization vectors to randomize each encryption. The Play encryption format has been changed to add support for initialization vectors.

The full name of the new AES transformation used by Play 2.4 is AES/CTR/NoPadding. The old transformation was AES/ECB/PKCS5Padding. The CTR mode is much more secure than the ECB mode. As before, you can override Play’s encryption transformation by setting the play.crypto.aes.transformation configuration option. In Play 2.4, any transformation supported by your JRE can be used, including transformations that use an initialization vector.

Play 2.4 uses a new encryption format, but it can read data encrypted by earlier versions of Play. However, earlier versions of Play will not be able to read data encrypted by Play 2.4. If your Play 2.4 application needs to produce data in the old format then you may want to copy the algorithm from the Play 2.3 Crypto code.

The table below shows the encryption formats supported by different versions of Play. Old format is used by older versions of Play. New format I is used by Play 2.4 if the configured cipher doesn’t use an initialization vector. New format II is used when an initialization vector is needed.

Format

Encoding

Play 2.3

Play 2.4

Old format

hex(cipher(plaintext))

writes

reads

reads

New format I

“1-” + base64(cipher(plaintext))

writes

reads

New format II

“2-” + base64(iv + cipher(plaintext, iv))

writes

reads

Usage of the Java Crypto API remains the same even though the output is different:

The configuration key to specify the languages that your application supports changed from application.langs to play.i18n.langs. Also, it is now a list instead of a comma separated string. Per instance:

You now need to have an implicit Messages value instead of just Lang in order to use the i18n API. The Messages type aggregates a Lang and a MessagesApi.

This means that you should change your templates to take an implicit Messages parameter instead of Lang:

@(form: Form[Login])(implicit messages: Messages)
...

From you controllers you can get such an implicit Messages value by mixing the play.api.i18n.I18nSupport trait in your controller that gives you an implicit Messages value as long as there is a RequestHeader value in the implicit scope. The I18nSupport trait has an abstract member def messagesApi: MessagesApi so your code will typically look like the following:

A simpler migration path is also supported if you want your controller to be still use static controller objects rather than injected classes with the I18nSupport trait. After modifying your templates to take an implicit Messages parameter, as described above, add the following import to your controllers:

import play.api.i18n.Messages.Implicits._

This import brings you an implicit Messages value as long as there are a Lang and an Application in the implicit scope (thankfully controllers already provide the Lang and you can get the currently running application by importing play.api.Play.current).

The API should be backward compatible with your code using Play 2.3 so there is no migration step. Nevertheless, note that you have to start your Play application before using the Java i18n API. That should always be the case when you run your project, however your test code may not always start your application. Please refer to the corresponding documentation page to know how to start your application before running your tests.

Previously, Play added all the resources to the conf directory in the distribution, but didn’t add the conf directory to the classpath. Now Play adds the conf directory to the classpath by default.

This can be turned off by setting PlayKeys.externalizeResources := false, which will cause no conf directory to be created in the distribution, and it will not be on the classpath. The contents of the applications conf directory will still be on the classpath by virtue of the fact that it’s included in the applications jar file.

Please note that if you’re using the Java Persistence API (JPA) you will need to set this option to false in order to deploy your application. Otherwise you will get an error message: Entity X is not mapped. This is not required for local development.

The sbt-native-packager has been upgraded. Due to this, the following adjustments might be necessary: * The syntax of the /etc/default/$appname file has changed from being a simple list of command line parameters to being a shell script that gets sourced by the start/stop scripts, allowing you to set environment variables. * The equivalent to the old syntax of the default file is an application.ini file in your archive’s conf folder. * The default-file gets sourced by SystemV Init scripts only - Upstart ignores this file right now. To change your build to create SystemV compatible packages, add this to your build.sbt:

The mysterious OrderedExecutionContext had been retained in Play for several versions in order to support legacy applications. It was rarely used and has now been removed. If you still need the OrderedExecutionContext for some reason, you can create your own implementation based on the Play 2.3 source. If you haven’t heard of this class, then there’s nothing you need to do.

Any assets in sub projects are now by default placed into /lib/[subproject] to allow files with the same name in the root project / different subprojects without causing them to interfere with each other.

To get the asset routing to work correctly in your app, you’ll need to change: