§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.
§Java 8 support
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:
java.lang.UnsupportedClassVersionError: play/runsupport/classloader/ApplicationClassLoaderProvider : Unsupported major.minor version 52.0
A java.lang.UnsupportedClassVersionError means that reading a Java class file with an older version of Java than the class file was compiled with is unsupported.
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.
§Build changes
The following steps need to be taken to update your sbt build before you can load/run a Play project in sbt.
§Play upgrade
Update the Play version number in project/plugins.sbt
to upgrade Play:
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.11")
§sbt upgrade
Play 2.4 now requires a minimum of sbt 0.13.8. Update your project/build.properties
so that it reads:
sbt.version=0.13.8
§Specs2 support in a separate module
If you were previously using Play’s specs2 support, you now need to explicitly add a dependency on that to your project. Additionally, specs2 now requires scalaz-stream
which isn’t available on maven central or any other repositories that sbt uses by default, so you need to add the scalaz-stream
repository as a resolver:
libraryDependencies += specs2 % Test
resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"
If you are using a .scala build file, you will need to add the following import import play.sbt.PlayImport._
§Database evolutions support in a separate module
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.
§IDEs: Eclipse and IntelliJ IDEA
Play no longer includes the sbteclipse or sbt-idea plugins, which enables users to upgrade IDE support independently of Play.
Eclipse support can be setup with as little as one extra line to import the plugin. See the documentation for details.
IntelliJ is now able to import sbt projects natively, so we recommend using that instead. Alternatively, the sbt-idea plugin can be manually installed and used, instructions can be found here.
§Play SBT plugin API
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.
§playWatchService
renamed
The SBT setting key playWatchService
has been renamed to fileWatchService
.
Also the corresponding class has changed. To set the FileWatchService to poll every two seconds, use it like this:
PlayKeys.fileWatchService := play.runsupport.FileWatchService.sbt(2000)
§Play Slick dependency
Play Slick module has gone through a major restyle to support Slick 3.0. For a smooth upgrade, read the Play Slick migration guide.
§Ebean dependency
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
:
addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "1.0.0")
After that, enable Ebean plugin for your project:
lazy val myProject = (project in file("."))
.enablePlugins(PlayJava, PlayEbean)
And finally, configure Ebean mapped classes as a list instead of a comma separated string (which is still supported but was deprecated):
ebean.default = ["models.*"]
ebean.orders = ["models.Order", "models.OrderItem"]
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
.
§Bytecode enhancement
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:
addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % "1.1.0")
§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 GeneratedRouter {
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 GeneratedRouter {
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 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.
§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 |
|
Crypto |
Crypto |
|
GlobalSettings |
HttpErrorHandler , HttpRequestHandler , and HttpFilters |
Read the details in the GlobalSettings section below. |
§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 |
|
Crypto |
Crypto |
The old static methods have been removed, an instance can statically be accessed using play.Play.application().injector().instanceOf(Crypto.class) |
GlobalSettings |
HttpErrorHandler , HttpRequestHandler , and HttpFilters |
Read the details in the GlobalSettings section below. |
§GlobalSettings
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 glory details.
§Plugin
deprecated
Both Java play.Plugin
and Scala play.api.Plugin
types have been deprecated. Read migrating Plugin to Module to update your code to use play.api.inject.Module
.
§Configuration changes
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:
Old key | New key |
---|---|
application.secret |
play.crypto.secret |
application.context |
play.http.context |
session.* |
play.http.session.* |
flash.* |
play.http.flash.* |
application.router |
play.http.router |
application.langs |
play.i18n.langs |
application.lang.cookie |
play.i18n.langCookieName |
parsers.text.maxLength |
play.http.parser.maxMemoryBuffer |
csrf |
play.filters.csrf |
evolutions.* |
play.evolutions.* |
applyEvolutions.<db> |
play.evolutions.db.<db>.autoApply |
ws |
play.ws |
§Akka configuration
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.
See the Java or Scala Akka page for more information.
§Thread pool configuration
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:
akka {
actor {
default-dispatcher {
fork-join-executor {
parallelism-factor = 1.0
parallelism-max = 24
task-peeking-mode = LIFO
}
}
}
}
In particular, you might want to try the default Akka settings:
akka {
actor {
default-dispatcher {
fork-join-executor {
parallelism-factor = 3.0
parallelism-max = 64
task-peeking-mode = FIFO
}
}
}
}
See the thread pool configuration section for more information.
§HttpRequestHandler
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:
play.http.requestHandler = "play.http.DefaultHttpRequestHandler"
§Logging
Logging is now configured solely via logback configuration files.
§JDBC connection pool
The default JDBC connection pool is now provided by HikariCP, instead of BoneCP.
To switch back to BoneCP, you can set the play.db.pool
property in application.conf
:
play.db.pool = bonecp
The full range of configuration options available to the Play connection pools can be found in the Play JDBC reference.conf
.
You may run into the following exception:
Caused by: java.sql.SQLException: JDBC4 Connection.isValid() method not supported, connection test query must be configured
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"
More information on this can be found on the HikariCP Github Page
§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, 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.
§JSON API changes
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.
val vOpt: Option[JsValue] = (json \ "foo" \ "bar").toOption
§JsLookup
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:
val foo: Option[FooBar] = (json \ "foo" \ "bar").asOpt[FooBar]
val bar: JsResult[Baz] = (json \ "baz").validate[Baz]
As a result of these changes, your code can now assume that all values of type JsValue
are serializable to JSON.
§Reading Options
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 toNone
, usejsv.validateOpt[A]
. This is typically what you want. - To map all validation errors to
None
, useJsSuccess(jsv.asOpt[A])
. This was what reading anOption
did in 2.3. - To map
JsNull
toNone
and validate the value if it exists, usejsv.validate(Reads.optionWithNull[A])
. If the value does not exist the result will be aJsError
.
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:
implicit val optionStringReads: Reads[Option[String]] = Reads.optionWithNull[String]
§Testing changes
FakeRequest
has been replaced by RequestBuilder
.
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
.
§Java TimeoutExceptions
If you use the Java API, the F.Promise
class now throws unchecked F.PromiseTimeoutException
s instead of Java’s checked TimeoutException
s. The TimeoutExceptions
s 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 |
§WS client
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 asplay.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
andws.ssl.disabledKeyAlgorithms
have been redefined as arrays of strings, e.gplay.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:
play.ws.ning.connectionTimeout
-> setConnectTimeoutplay.ws.ning.idleTimeout
-> setReadTimeoutplay.ws.ning.followRedirects
-> setFollowRedirectplay.ws.ning.compressionEnabled
-> setCompressionEnforcedplay.ws.ning.allowPoolingConnection
-> setAllowPoolingConnectionsplay.ws.ning.allowSslConnectionPool
-> setAllowPoolingSslConnectionsplay.ws.ning.maxConnectionsTotal
-> setMaxConnectionsplay.ws.ning.maxConnectionLifetime
-> setConnectionTTLplay.ws.ning.idleConnectionInPoolTimeout
-> setPooledConnectionIdleTimeoutplay.ws.ning.webSocketIdleTimeout
-> setWebSocketTimeoutplay.ws.ning.maxNumberOfRedirects
-> setMaxRedirectsplay.ws.ning.disableUrlEncoding
-> setDisableUrlEncodingForBoundedRequests
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.
§Crypto APIs
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:
import play.libs.Crypto;
String enc = Crypto.encryptAES(orig);
String dec = Crypto.decryptAES(enc);
Usage of the Scala Crypto API is also the same:
import play.api.libs.Crypto
val enc = Crypto.encryptAES(orig)
val dec = Crypto.decryptAES(enc)
§Anorm
The new Anorm version includes various fixes and improvements. Read here to find out more.
§HTTP server configuration
Advanced Netty configuration options, that is, options prefixed with http.netty.option
, must now use the prefix play.server.netty.option
instead.
§I18n
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:
play.i18n.langs = [ "en", "en-US", "fr" ]
§Scala
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:
import javax.inject.Inject
import play.api.i18n.{MessagesApi, I18nSupport}
import play.api.mvc.Controller
class MyController @Inject() (val messagesApi: MessagesApi)
extends Controller with I18nSupport {
}
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
).
§Java
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.
§Distribution
Previously, Play added all the resources to the 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.
§Changes in Debian Package creation
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:
import com.typesafe.sbt.packager.archetypes.ServerLoader.{SystemV, Upstart}
serverLoading in Debian := SystemV
- Other changes that might be necessary can be found in the sbt-native-packager release notes.
§Miscellaneous
§No more OrderedExecutionContext
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.
§SubProject Assets
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:
GET /assets/*file controllers.myModule.Assets.at(path="/public", file)
to this
GET /assets/*file controllers.myModule.Assets.at(path="/public/lib/myModule", file)