§Play 2.6 Migration Guide
This is a guide for migrating from Play 2.5 to Play 2.6. If you need to migrate from an earlier version of Play then you must first follow the Play 2.5 Migration Guide.
§How to migrate
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.6.x")
Where the “x” in 2.6.x
is the minor version of Play you want to use, for instance 2.6.0
.
§sbt upgrade to 0.13.15
Play 2.6 requires upgrading to at least sbt 0.13.15. The 0.13.15 release of sbt has a number of improvements and bug fixes (see also the changes in sbt 0.13.13).
sbt 1.x is supported beginning with Play 2.6.6. If you are using other sbt plugins, you may need to check if there is a newer version compatible with sbt 1.x
To update, change your project/build.properties
so that it reads:
sbt.version=0.13.15
§Guice DI support moved to separate module
In Play 2.6, the core Play module no longer includes Guice. You will need to configure the Guice module by adding guice
to your libraryDependencies
:
libraryDependencies += guice
§OpenID support moved to separate module
In Play 2.6, the core Play module no longer includes the OpenID support in play.api.libs.openid
(Scala) and play.libs.openid
(Java). To use these packages add openId
to your libraryDependencies
:
libraryDependencies += openId
§Play JSON moved to separate project
Play JSON has been moved to a separate library hosted at https://github.com/playframework/play-json. Since Play JSON has no dependencies on the rest of Play, the main change is that the json
value from PlayImport
will no longer work in your sbt build. Instead, you’ll have to specify the library manually:
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.0"
Also, play-json has a separate release cycle from the core Play library, so the version is no longer in sync with the Play version.
§Play Iteratees moved to separate project
Play Iteratees has been moved to a separate library hosted at https://github.com/playframework/play-iteratees. Since Play Iteratees has no dependencies on the rest of Play, the main change is that the you’ll have to specify the library manually:
libraryDependencies += "com.typesafe.play" %% "play-iteratees" % "2.6.1"
The project also has a sub project that integrates Iteratees with Reactive Streams. You may need to add the following dependency as well:
libraryDependencies += "com.typesafe.play" %% "play-iteratees-reactive-streams" % "2.6.1"
Note: The helper class
play.api.libs.streams.Streams
was moved toplay-iteratees-reactive-streams
and now is calledplay.api.libs.iteratee.streams.IterateeStreams
. So you may need to add the Iteratees dependencies and also use the new class where necessary.
Finally, Play Iteratees has a separate versioning scheme, so the version is no longer in sync with the Play version.
§Akka HTTP as the default server engine
Play now uses the Akka-HTTP server engine as the default backend. If you need to change it back to Netty for some reason (for example, if you are using Netty’s native transports), see how to do that in Netty Server documentation.
You can read more at Akka HTTP Server Backend.
§Akka HTTP server timeouts
Play 2.5.x does not have a request timeout configuration for Netty Server, which was the default server backend. But Akka HTTP has timeouts for both idle connections and requests (see more details in Akka HTTP Settings documentation). Akka HTTP docs states that:
Akka HTTP comes with a variety of built-in timeout mechanisms to protect your servers from malicious attacks or programming mistakes.
And you can see the default values for akka.http.server.idle-timeout
, akka.http.server.request-timeout
and akka.http.server.bind-timeout
here. Play has its own configurations to define timeouts, so if you start to see a number of 503 Service Unavailable
, you can change the configurations to values that are more reasonable to your application, for example:
play.server.http.idleTimeout = 60s
play.server.akka.requestTimeout = 40s
§Scala Mode
changes
Scala Mode
was refactored from an Enumeration to a hierarchy of case objects. Most of the Scala code won’t change because of this refactoring. But, if you are accessing the Scala Mode
values in your Java code, you will need to change it from:
// Consider this Java code
play.api.Mode scalaMode = play.api.Mode.Test();
Must be rewritten to:
// Consider this Java code
play.api.Mode scalaMode = play.Mode.TEST.asScala();
It is also easier to convert between Java and Scala modes:
// In your Java code
play.api.Mode scalaMode = play.Mode.DEV.asScala();
Or in your Scala code:
play.Mode javaMode = play.api.Mode.Dev.asJava
Also, play.api.Mode.Mode
is now deprecated and you should use play.api.Mode
instead.
§Writeable[JsValue]
changes
Previously, the default Scala Writeable[JsValue]
allowed you to define an implicit Codec
, which would allow you to write using a different charset. This could be a problem since application/json
does not act like text-based content types. It only allows Unicode charsets (UTF-8
, UTF-16
and UTF-32
) and does not define a charset
parameter like many text-based content types.
Now, the default Writeable[JsValue]
takes no implicit parameters and always writes to UTF-8
. This covers the majority of cases, since most users want to use UTF-8 for JSON. It also allows us to easily use more efficient built-in methods for writing JSON to a byte array.
If you need the old behavior back, you can define a Writeable
with an arbitrary codec using play.api.http.Writeable.writeableOf_JsValue(codec, contentType)
for your desired Codec and Content-Type.
§Scala Controller changes
The idiomatic Play controller has in the past required global state. The main places that was needed was in the global play.api.mvc.Action
object and BodyParsers#parse
method.
We have provided several new controller classes with new ways of injecting that state, providing the same syntax:
- BaseController
: a trait with an abstract ControllerComponents
that can be provided by an implementing class.
- AbstractController
: an abstract class extending BaseController
with a ControllerComponents
constructor parameter that can be injected using constructor injection.
- InjectedController
: a trait, extending BaseController
, that obtains the ControllerComponents
through method injection (calling a setControllerComponents
method). If you are using a runtime DI framework like Guice, this is done automatically.
ControllerComponents
is simply meant to bundle together components typically used in a controller. You may also wish to create your own base controller for your app by extending ControllerHelpers
and injecting your own bundle of components. Play does not require your controllers to implement any particular trait.
Note that BaseController
makes Action
and parse
refer to injected instances rather than the global objects, which is usually what you want to do.
Here’s an example of code using AbstractController
:
class FooController @Inject() (components: ControllerComponents)
extends AbstractController(components) {
// Action and parse now use the injected components
def foo = Action(parse.json) {
Ok
}
}
and using BaseController
:
class FooController @Inject() (val controllerComponents: ControllerComponents) extends BaseController {
// Action and parse now use the injected components
def foo = Action(parse.json) {
Ok
}
}
and InjectedController
:
class FooController @Inject() () extends InjectedController {
// Action and parse now use the injected components
def foo = Action(parse.json) {
Ok
}
}
InjectedController
gets its ControllerComponents
by calling the setControllerComponents
method, which is called automatically by JSR-330 compliant dependency injection. We do not recommend using InjectedController
with compile-time injection. If you plan to extensively unit test your controllers manually, we also recommend avoiding InjectedController
since it hides the dependency.
If you prefer to pass the individual dependencies manually, you can do that instead and extend ControllerHelpers
, which has no dependencies or state. Here’s an example:
class Controller @Inject() (
action: DefaultActionBuilder,
parse: PlayBodyParsers,
messagesApi: MessagesApi
) extends ControllerHelpers {
def index = action(parse.text) { request =>
Ok(messagesApi.preferred(request)("hello.world"))
}
}
§Scala ActionBuilder and BodyParser changes
The Scala ActionBuilder
trait has been modified to specify the type of the body as a type parameter, and add an abstract parser
member as the default body parsers. You will need to modify your ActionBuilders and pass the body parser directly.
The play.api.mvc.Action
global object and BodyParsers#parse
are now deprecated. They are replaced by injectable traits, DefaultActionBuilder
and PlayBodyParsers
respectively. If you are inside a controller, they are automatically provided by the new BaseController
trait (see the controller changes above).
§Cookies
For Java users, we now recommend using play.mvc.Http.Cookie.builder
to create new cookies, for example:
Http.Cookie cookie = Cookie.builder("color", "blue")
.withMaxAge(3600)
.withSecure(true)
.withHttpOnly(true)
.withSameSite(SameSite.STRICT)
.build();
This is more readable than a plain constructor call, and will be source-compatible if we add/remove cookie attributes in the future.
§SameSite attribute, enabled for session and flash
Cookies now can have an additional SameSite
attribute, which can be used to prevent CSRF. There are three possible states:
- No
SameSite
, meaning cookies will be sent for all requests to that domain. SameSite=Strict
, meaning the cookie will only be sent for same-site requests (coming from another page on the site) not cross-site requestsSameSite=Lax
, meaning the cookie will be sent for cross-site requests as top-level navigation, but otherwise only for same-site requests. This will do the correct thing for most sites, but won’t prevent certain types of attacks, such as those executed by launching popup windows.
In addition, we have moved the session and flash cookies to use SameSite=Lax
by default. You can tweak this using configuration. For example:
play.http.session.sameSite = null // no same-site for session
play.http.flash.sameSite = "strict" // strict same-site for flash
Note: this feature is currently not supported by many browsers, so you should not rely on it. Chrome and Opera are the only major browsers to support SameSite right now.
§__Host and __Secure prefixes
We’ve also added support for the __Host and __Secure cookie name prefixes.
This will only affect you if you happen to be using these prefixes for cookie names. If you are, Play will warn when serializing and deserializing those cookies if the proper attributes are not set, then set them for you automatically. To remove the warning, either cease using those prefixes for your cookies, or be sure to set the attributes as follows:
- Cookies named with
__Host-
should setPath=/
andSecure
attributes. - Cookies named with
__Secure-
should set theSecure
attribute.
§Assets
§Binding Assets with compile-time DI
If you are using compile-time DI, you should mix in controllers.AssetsComponents
and use that to obtain the assets: Assets
controller instance:
class MyComponents(context: Context) extends BuiltInComponentsFromContext(context) with AssetsComponents {
lazy val router = new Routes(httpErrorHandler, assets)
}
If you have an existing lazy val assets: Assets
you can remove it.
§Assets configuration
Existing user-facing APIs have not changed, but we suggest moving over to the AssetsFinder
API for finding assets and setting up your assets directories in configuration:
play.assets {
path = "/public"
urlPrefix = "/assets"
}
Then in routes you can do:
# prefix must match `play.assets.urlPrefix`
GET /assets/*file controllers.Assets.at(file)
GET /versionedAssets/*file controllers.Assets.versioned(file)
You no longer need to provide an assets path at the start of the argument list, since that’s now read from configuration.
Then in your template you can use AssetsFinder#path
to find the final path of the asset:
@(assets: AssetsFinder)
<img alt="hamburger" src="@assets.path("images/hamburger.jpg")">
You can still continue to use reverse routes with Assets.versioned
, but some global state is required to convert the asset name you provide to the final asset name, which can be problematic if you want to run multiple applications at once.
§Form changes
Starting with Play 2.6, query string parameters will not be bound to a form instance anymore when using bindFromRequest()
in combination with POST
, PUT
or PATCH
requests.
Static methods which where already deprecated in 2.5 (e.g. DynamicForm.form()
) where removed in this release. Refer to the Play 2.5 Migration Guide for details on how to migrate, in case you still use them.
§Java Form Changes
The errors()
method of a play.data.Form
instance is now deprecated. You should use allErrors()
instead now which returns a simple List<ValidationError>
instead of a Map<String,List<ValidationError>>
. Where before Play 2.6 you called .errors().get("key")
you can now simply call .errors("key")
.
From now on, a validate
method implemented inside a form class (usually used for cross field validation) is part of a class-level constraint. Check out the Advanced validation docs for further information on how to use such constraints.
Existing validate
methods can easily be migrated by annotating the affected form classes with @Validate
and, depending on the return type of the validate method, by implementing the Validatable
interface with the applicable type argument (all defined in play.data.validation.Constraints
):
Return type | Interface to implement |
---|---|
String |
Validatable<String> |
ValidationError |
Validatable<ValidationError> |
List<ValidationError> |
Validatable<List<ValidationError>> |
Map<String,List<ValidationError>> (not supported anymore; use List instead) |
Validatable<List<ValidationError>> |
For example an existing form like:
public class MyForm {
//...
public String validate() {
//...
}
}
Has to be changed to:
import play.data.validation.Constraints.Validate;
import play.data.validation.Constraints.Validatable;
@Validate
public class MyForm implements Validatable<String> {
//...
@Override
public String validate() {
//...
}
}
Be aware: The “old”
validate
method was invoked only after all other constraints were successful before. By default class-level constraints however are called simultaneously with any other constraint annotations - no matter if they passed or failed. To (also) define an order between the constraints you can now use constraint groups.
§JPA Migration Notes
See JPA migration notes.
§I18n Migration Notes
See I18N API Migration.
§Cache APIs Migration Notes
See Cache APIs Migration.
§Java Configuration API Migration Notes
See Java Configuration Migration.
§Scala Configuration API
The Scala play.api.Configuration
API now has new methods that allow loading any type using a ConfigLoader
. These new methods expect configuration keys to exist in the configuration file. For example, the following old code:
val myConfig: String = configuration.getString("my.config.key").getOrElse("default")
should be changed to
val myConfig: String = configuration.get[String]("my.config.key")
and the value “default” should be set in configuration as my.config.key = default
.
Alternatively, if custom logic is required in the code to obtain the default value, you can set the default to null in your config file (my.config.key = null
), and read an Option[T]
:
val myConfigOption: Option[String] = configuration.get[Option[String]]("my.config.key")
val myConfig: String = myConfigOption.getOrElse(computeDefaultValue())
Also, there are several methods in the old play.api.Configuration
that return Java types, like getBooleanList
. We recommend using the Scala version get[Seq[Boolean]]
instead if possible. If that is not possible, you can access the underlying
Config object and call getBooleanList
from it.
The deprecation messages on the existing methods also explain how to migrate each method. See the Scala Configuration docs for more details on the proper use of play.api.Configuration
.
§Play JSON API changes
§JSON array index lookup
If you are using the Scala play-json API, there was a small change in the way the JsLookup
implicit class works. For example, if you have code like:
val bar = (jsarray(index) \ "bar").as[Bar]
where index
is an array index and jsarray
is a JsArray
, now you should write:
val bar = (jsarray \ index \ "bar").as[Bar]
This was done to bring the behavior of indexing on JsArray
s in line with that of other collections in Scala. Now the jsarray(index)
method will return the value at the index, throwing an exception if it does not exist.
Also, there is small change in play-json API, play.api.data.validation.ValidationError
has been changed to play.api.libs.json.JsonValidationError
. For example, if you have code like:
ValidationError("Validation Error")
where “Validation Error” is the message, now you should write:
JsonValidationError("Validation Error")
§Removed APIs
§Removed Crypto API
The Crypto API has removed the deprecated classes play.api.libs.Crypto
, play.libs.Crypto
and AESCTRCrypter
. The CSRF references to Crypto
have been replaced by CSRFTokenSigner
. The session cookie references to Crypto
have been replaced with CookieSigner
. Please see CryptoMigration25 for more information.
§Akka
deprecated methods removed
The deprecated static methods play.libs.Akka.system
and play.api.libs.concurrent.Akka.system
were removed. Use dependency injection to get an instance of ActorSystem
and access to the actor system.
For Scala:
class MyComponent @Inject() (system: ActorSystem) {
}
And for Java:
public class MyComponent {
private final ActorSystem system;
@Inject
public MyComponent(ActorSystem system) {
this.system = system;
}
}
Also, Play 2.6.x now uses the Akka 2.5.x release series. Read Akka migration guide from 2.4.x to 2.5.x to see how to adapt your own code if necessary.
§Removed Yaml API
We removed play.libs.Yaml
since there was no use of it inside of play anymore. If you still need support for the Play YAML integration you need to add snakeyaml
in you build.sbt
:
libraryDependencies += "org.yaml" % "snakeyaml" % "1.17"
And create the following Wrapper in your Code:
public class Yaml {
private final play.Environment environment;
@Inject
public Yaml(play.Environment environment) {
this.environment = environment;
}
/**
* Load a Yaml file from the classpath.
*/
public Object load(String resourceName) {
return load(
environment.resourceAsStream(resourceName),
environment.classLoader()
);
}
/**
* Load the specified InputStream as Yaml.
*
* @param classloader The classloader to use to instantiate Java objects.
*/
public Object load(InputStream is, ClassLoader classloader) {
org.yaml.snakeyaml.Yaml yaml = new org.yaml.snakeyaml.Yaml(new CustomClassLoaderConstructor(classloader));
return yaml.load(is);
}
}
Or in Scala:
class Yaml @Inject()(environment: play.api.Environment) {
def load(resourceName: String) = {
load(environment.resourceAsStream(resourceName), environment.classLoader)
}
def load(inputStream: InputStream, classLoader: ClassLoader) = {
new org.yaml.snakeyaml.Yaml(new CustomClassLoaderConstructor(classloader)).load(inputStream)
}
}
If you explicitly depend on an alternate DI library for Play, or have defined your own custom application loader, no changes should be required.
Libraries that provide Play DI support should define the play.application.loader
configuration key. If no external DI library is provided, Play will refuse to start unless you point that to an ApplicationLoader
.
§Removed deprecated play.Routes
The deprecated play.Routes
class used to create a JavaScript router were removed. You now have to use the new Java or Scala helpers:
§Removed libraries
In order to make the default play distribution a bit smaller we removed some libraries. The following libraries are no longer dependencies in Play 2.6, so you will need to manually add them to your build if you use them.
§Joda-Time removal
We recommend using the java.time
APIs, so we are removing joda-time support from the core of Play.
Play’s Scala forms library had some Joda formats. If you don’t wish to migrate, you can add the jodaForms
module in your build.sbt
:
libraryDependencies += jodaForms
And then import the corresponding object:
import play.api.data.JodaForms._
If you need Joda support in play-json, you can add the following dependency:
libraryDependencies += "com.typesafe.play" %% "play-json-joda" % playJsonVersion
where playJsonVersion
is the play-json version you wish to use. Play 2.6.x should be compatible with play-json 2.6.x. Note that play-json is now a separate project (described later).
import play.api.libs.json.JodaWrites._
import play.api.libs.json.JodaReads._
§Joda-Convert removal
Play had some internal uses of joda-convert
if you used it in your project you need to add it to your build.sbt
:
libraryDependencies += "org.joda" % "joda-convert" % "1.8.1"
§XercesImpl removal
For XML handling Play used the Xerces XML Library. Since modern JVM are using Xerces as a reference implementation we removed it. If your project relies on the external package you can simply add it to your build.sbt
:
libraryDependencies += "xerces" % "xercesImpl" % "2.11.0"
§H2 removal
Prior versions of Play prepackaged the H2 database. But to make the core of Play smaller we removed it. If you make use of H2 you can add it to your build.sbt
:
libraryDependencies += "com.h2database" % "h2" % "1.4.193"
If you only used it in your test you can also just use the Test
scope:
libraryDependencies += "com.h2database" % "h2" % "1.4.193" % Test
The H2 Browser will still work after you added the dependency.
§snakeyaml removal
Play removed play.libs.Yaml
and therefore the dependency on snakeyaml
was dropped. If you still use it add it to your build.sbt
:
libraryDependencies += "org.yaml" % "snakeyaml" % "1.17"
See also notes about the removal of Yaml API.
§Tomcat-servlet-api removal
Play removed the tomcat-servlet-api
since it was of no use. If you still use it add it to your build.sbt
:
libraryDependencies += "org.apache.tomcat" % "tomcat-servlet-api" % "8.0.33"
§fork-run removal
The sbt-fork-run-plugin
will no longer be generated, as it was only needed for the now end-of-life activator utility. As it will no longer resolve for 2.6 it can safely be removed altogether.
§Request attributes
All request objects now contain attributes. Request attributes are a replacement for request tags. Tags have now been deprecated and you should upgrade to attributes. Attributes are more powerful than tags; you can use attributes to store objects in requests, whereas tags only supported storing Strings.
§Request tags deprecation
Tags have been deprecated so you should start migrating from using tags to using attributes. Migration should be fairly straightforward.
The easiest migration path is to migrate from a tag to an attribute with a String
type.
Java before:
// Getting a tag from a Request or RequestHeader
String userName = req.tags().get("userName");
// Setting a tag on a Request or RequestHeader
req.tags().put("userName", newName);
// Setting a tag with a RequestBuilder
Request builtReq = requestBuilder.tag("userName", newName).build();
Java after:
class Attrs {
public static final TypedKey<String> USER_NAME = TypedKey.<String>create("userName");
}
// Getting an attribute from a Request or RequestHeader
String userName = req.attrs().get(Attrs.USER_NAME);
String userName = req.attrs().getOptional(Attrs.USER_NAME);
// Setting an attribute on a Request or RequestHeader
Request newReq = req.withTags(req.tags().put(Attrs.USER_NAME, newName));
// Setting an attribute with a RequestBuilder
Request builtReq = requestBuilder.attr(Attrs.USER_NAME, newName).build();
Scala before:
// Getting a tag from a Request or RequestHeader
val userName: String = req.tags("userName")
val optUserName: Option[String] = req.tags.get("userName")
// Setting a tag on a Request or RequestHeader
val newReq = req.copy(tags = req.tags.updated("userName", newName))
Scala after:
object Attrs {
val UserName: TypedKey[String] = TypedKey("userName")
}
// Getting an attribute from a Request or RequestHeader
val userName: String = req.attrs(Attrs.UserName)
val optUserName: [String] = req.attrs.get(Attrs.UserName)
// Setting an attribute on a Request or RequestHeader
val newReq = req.addAttr(Attrs.UserName, newName)
However, if appropriate, we recommend you convert your String
tags into attributes with non-String
values. Converting your tags into non-String
objects has several benefits. First, you will make your code more type-safe. This will increase your code’s reliability and make it easier to understand. Second, the objects you store in attributes can contain multiple properties, allowing you to aggregate multiple tags into a single value. Third, converting tags into attributes means you don’t need to encode and decode values from String
s, which may increase performance.
class Attrs {
public static final TypedKey<User> USER = TypedKey.<User>create("user");
}
Scala after:
object Attrs {
val UserName: TypedKey[User] = TypedKey("user")
}
§Calling FakeRequest.withCookies
no longer updates the Cookies
header
Request cookies are now stored in a request attribute. Previously they were stored in the request’s Cookie
header String
. This required encoding and decoding the cookie to the header whenever the cookie changed.
Now that cookies are stored in request attributes updating the cookie will change the new cookie attribute but not the Cookie
HTTP header. This will only affect your tests if you’re relying on the fact that calling withCookies
will update the header.
If you still need the old behavior you can still use Cookies.encodeCookieHeader
to convert the Cookie
objects into an HTTP header then store the header with FakeRequest.withHeaders
.
§play.api.mvc.Security.username
(Scala API), session.username
changes
play.api.mvc.Security.username
(Scala API), session.username
config key and dependent actions helpers are deprecated. Security.username
just retrieves the session.username
key from configuration, which defined the session key used to get the username. It was removed since it required statics to work, and it’s fairly easy to implement the same or similar behavior yourself.
You can read the username session key from configuration yourself using configuration.get[String]("session.username")
.
If you’re using the Authenticated(String => EssentialAction)
method, you can easily create your own action to do something similar:
def AuthenticatedWithUsername(action: String => EssentialAction) =
WithAuthentication[String](_.session.get(UsernameKey))(action)
where UsernameKey
represents the session key you want to use for the username.
§Request Security (Java API) username property is now an attribute
The Java Request object contains a username
property which is set when the Security.Authenticated
annotation is added to a Java action. In Play 2.6 the username property has been deprecated. The username property methods have been updated to store the username in the Security.USERNAME
attribute. You should update your code to use the Security.USERNAME
attribute directly. In a future version of Play we will remove the username property.
The reason for this change is that the username property was provided as a special case for the Security.Authenticated
annotation. Now that we have attributes we don’t need a special case anymore.
Existing Java code:
// Set the username
Request reqWithUsername = req.withUsername("admin");
// Get the username
String username = req1.username();
// Set the username with a builder
Request reqWithUsername = new RequestBuilder().username("admin").build();
Updated Java code:
import play.mvc.Security.USERNAME;
// Set the username
Request reqWithUsername = req.withAttr(USERNAME, "admin");
// Get the username
String username = req1.attr(USERNAME);
// Set the username with a builder
Request reqWithUsername = new RequestBuilder().putAttr(USERNAME, "admin").build();
§Router tags are now attributes
If you used any of the Router.Tags.*
tags, you should change your code to use the new Router.Attrs.HandlerDef
(Scala) or Router.Attrs.HANDLER_DEF
(Java) attribute instead. The existing tags are still available, but are deprecated and will be removed in a future version of Play.
This new attribute contains a HandlerDef
object with all the information that is currently in the tags. The current tags all correspond to a field in the HandlerDef
object:
Java tag name | Scala tag name | HandlerDef method |
---|---|---|
ROUTE_PATTERN |
RoutePattern |
path |
ROUTE_VERB |
RouteVerb |
verb |
ROUTE_CONTROLLER |
RouteController |
controller |
ROUTE_ACTION_METHOD |
RouteActionMethod |
method |
ROUTE_COMMENTS |
RouteComments |
comments |
Note: As part of this change the
HandlerDef
object has been moved from theplay.core.routing
internal package into theplay.api.routing
public API package.
§play.api.libs.concurrent.Execution
is deprecated
The play.api.libs.concurrent.Execution
class has been deprecated, as it was using global mutable state under the hood to pull the “current” application’s ExecutionContext.
If you want to specify the implicit behavior that you had previously, then you should pass in the execution context implicitly in the constructor using dependency injection:
class MyController @Inject()(implicit ec: ExecutionContext) {
}
or from BuiltInComponents if you are using compile time dependency injection:
class MyComponentsFromContext(context: ApplicationLoader.Context)
extends BuiltInComponentsFromContext(context) {
val myComponent: MyComponent = new MyComponent(executionContext)
}
However, there are some good reasons why you may not want to import an execution context even in the general case. In the general case, the application’s execution context is good for rendering actions, and executing CPU-bound activities that do not involve blocking API calls or I/O activity. If you are calling out to a database, or making network calls, then you may want to define your own custom execution context.
The recommended way to create a custom execution context is through CustomExecutionContext
, which uses the Akka dispatcher system (java / scala) so that executors can be defined through configuration.
To use your own execution context, extend the CustomExecutionContext
abstract class with the full path to the dispatcher in the application.conf
file:
import play.api.libs.concurrent.CustomExecutionContext
class MyExecutionContext @Inject()(actorSystem: ActorSystem)
extends CustomExecutionContext(actorSystem, "my.dispatcher.name")
import play.libs.concurrent.CustomExecutionContext;
class MyExecutionContext extends CustomExecutionContext {
@Inject
public MyExecutionContext(ActorSystem actorSystem) {
super(actorSystem, "my.dispatcher.name");
}
}
and then inject your custom execution context as appropriate:
class MyBlockingRepository @Inject()(implicit myExecutionContext: MyExecutionContext) {
// do things with custom execution context
}
Please see ThreadPools page for more information on custom thread pool configuration, and JavaAsync / ScalaAsync for using CustomExecutionContext
.
§Changes to play.api.test Helpers
The following deprecated test helpers have been removed in 2.6.x:
play.api.test.FakeApplication
has been replaced byplay.api.inject.guice.GuiceApplicationBuilder
.- The
play.api.test.Helpers.route(request)
has been replaced with theplay.api.test.Helpers.routes(app, request)
method. - The
play.api.test.Helpers.route(request, body)
has been replaced with theplay.api.test.Helpers.routes(app, request, body)
method.
§Java API
play.test.FakeRequest
has been replaced byRequestBuilder
play.test.FakeApplication
has been replaced withplay.inject.guice.GuiceApplicationBuilder
. You can create a newApplication
fromplay.test.Helpers.fakeApplication
.- In
play.test.WithApplication
, the deprecatedprovideFakeApplication
method has been removed – theprovideApplication
method should be used.
§Changes to Template Helpers
The requireJs
template helper in views/helper/requireJs.scala.html
used Play.maybeApplication
to access the configuration.
The requireJs
template helper has an extra parameter isProd
added to it that indicates whether the minified version of the helper should be used:
@requireJs(core = routes.Assets.at("javascripts/require.js").url, module = routes.Assets.at("javascripts/main").url, isProd = true)
§Changes to File Extension to MIME Type Mapping
The mapping of file extensions to MIME types has been moved to reference.conf
so it is covered entirely through configuration, under play.http.fileMimeTypes
setting. Previously the list was hardcoded under play.api.libs.MimeTypes
.
Note that play.http.fileMimeTypes
configuration setting is defined using triple quotes as a single string – this is because several file extensions have syntax that breaks HOCON, such as c++
.
To append a custom MIME type, use HOCON string value concatenation:
play.http.fileMimeTypes = ${play.http.fileMimeTypes} """
foo=text/bar
"""
There is a syntax that allows configurations defined as mimetype.foo=text/bar
for additional MIME types. This is deprecated, and you are encouraged to use the above configuration.
§Java API
There is a Http.Context.current().fileMimeTypes()
method that is provided under the hood to Results.sendFile
and other methods that look up content types from file extensions. No migration is necessary.
§Scala API
The play.api.libs.MimeTypes
class has been changed to play.api.http.FileMimeTypes
interface, and the implementation has changed to play.api.http.DefaultFileMimeTypes
.
All the results that send files or resources now take FileMimeTypes
implicitly, i.e.
implicit val fileMimeTypes: FileMimeTypes = ...
Ok(file) // <-- takes implicit FileMimeTypes
An implicit instance of FileMimeTypes
is provided by BaseController
(and its subclass AbstractController
and subtrait InjectedController
) through the ControllerComponents
class, to provide a convenient binding:
class SendFileController @Inject() (cc: ControllerComponents) extends AbstractController(cc) {
def index() = Action { implicit request =>
val file = readFile()
Ok(file) // <-- takes implicit FileMimeTypes
}
}
You can also get a fully configured FileMimeTypes
instance directly in a unit test:
val httpConfiguration = new HttpConfigurationProvider(Configuration.load(Environment.simple)).get
val fileMimeTypes = new DefaultFileMimeTypesProvider(httpConfiguration.fileMimeTypes).get
Or get a custom one:
val fileMimeTypes = new DefaultFileMimeTypesProvider(FileMimeTypesConfiguration(Map("foo" -> "text/bar"))).get
§Default Filters
Play now comes with a default set of enabled filters, defined through configuration. If the property play.http.filters
is null, then the default is now play.api.http.EnabledFilters
, which loads up the filters defined by fully qualified class name in the play.filters.enabled
configuration property.
In Play itself, play.filters.enabled
is an empty list. However, the filters library is automatically loaded in sbt as an AutoPlugin called PlayFilters
, and will append the following values to the play.filters.enabled
property:
play.filters.csrf.CSRFFilter
play.filters.headers.SecurityHeadersFilter
play.filters.hosts.AllowedHostsFilter
This means that on new projects, CSRF protection (ScalaCsrf / JavaCsrf), SecurityHeaders and AllowedHostsFilter are all defined automatically.
§Effects of Default Filters
The default filters are configured to give a “secure by default” configuration to projects.
You should keep these filters enabled: they make your application more secure.
If you did not have these filters enabled in an existing project, then there is some configuration required, and you may not be familiar with the errors and failures involved. To help with migration, we’ll go over each filter, what it does and what configuration is required.
§CSRFFilter
The CSRF filter is described in ScalaCsrf and JavaCsrf. It protects against cross site request forgery attacks, by adding a CSRF token to forms that is checked on POST requests.
§Why it is enabled by default
CSRF is a very common attack that takes very little skill to implement. You can see an example of a CSRF attack using Play at https://github.com/Manc/play-scala-csrf.
§What changes do I need to make?
If you are migrating from an existing project that does not use CSRF form helpers such as CSRF.formField
, then you may see “403 Forbidden” on PUT and POST requests from the CSRF filter.
Adding CSRF.formField
to your form templates will resolve the error If you are making requests with AJAX, you can place the CSRF token in the HTML page, and then add it to the request using the Csrf-Token
header.
To check this behavior, please add <logger name="play.filters.csrf" value="TRACE"/>
to your logback.xml
.
You may also want to enable SameSite cookies in Play, which provide an additional defense against CSRF attacks.
§SecurityHeadersFilter
SecurityHeadersFilter prevents cross site scripting and clickjacking attacks, by adding extra HTTP headers to the request.
§Why it is enabled by default
Browser based attacks are extremely common, and security headers can provide a defense in depth to help frustrate those attacks.
§What changes do I need to make?
The default “Content-Security-Policy” settings are quite strict, and it is likely that you will need to experiment with it to find the most useful settings. The Content-Security-Policy settings will change how Javascript and remote frames are displayed in a browser. Embedded Javascript or CSS will not be loaded in your web page until you modify the Content-Security-Policy header.
If you are sure that you do not want to enable it, you can disable the Content-Security-Policy as follows:
play.filters.headers.contentSecurityPolicy=null
CSP-Useful is a good resource on Content-Security-Policy in general. Note that there are other potential solutions to embedded Javascript, such as adding a custom CSP nonce on every request.
The other headers are less intrusive, and are unlikely to cause problems on a plain website, but may cause cookie or rendering problems on a Single Page Application. Mozilla has documentation describing each header in detail, using the header name in the URL: for example, for X-Frame-Options go to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options.
play.filters.headers {
# The X-Frame-Options header. If null, the header is not set.
frameOptions = "DENY"
# The X-XSS-Protection header. If null, the header is not set.
xssProtection = "1; mode=block"
# The X-Content-Type-Options header. If null, the header is not set.
contentTypeOptions = "nosniff"
# The X-Permitted-Cross-Domain-Policies header. If null, the header is not set.
permittedCrossDomainPolicies = "master-only"
# The Content-Security-Policy header. If null, the header is not set.
contentSecurityPolicy = "default-src 'self'"
# The Referrer-Policy header. If null, the header is not set.
referrerPolicy = "origin-when-cross-origin, strict-origin-when-cross-origin"
# If true, allow an action to use .withHeaders to replace one or more of the above headers
allowActionSpecificHeaders = false
}
§AllowedHostsFilter
The AllowedHostsFilter adds a whitelist of allowed hosts and sends a 400 (Bad Request) response to all requests with a host that do not match the whitelist.
§Why it is enabled by default
This is an important filter to use in development, because DNS rebinding attacks can be used against a developer’s instance of Play: see Rails Webconsole DNS Rebinding for an example of how short lived DNS rebinding can attack a server running on localhost.
§What changes do I need to make?
If you are running a Play application on something other than localhost, you must configure the AllowedHostsFilter to specifically allow the hostname/ip you are connecting from. This is especially important to note when you change environments, because typically you’ll run on localhost in development, but will run remotely in staging and production.
play.filters.hosts {
# Allow requests to example.com, its subdomains, and localhost:9000.
allowed = [".example.com", "localhost:9000"]
}
§Appending To Filters
To append to the defaults list, use the +=
:
play.filters.enabled+=MyFilter
If you have defined your own filters by extending play.api.http.DefaultHttpFilters
, then you can also combine EnabledFilters
with your own list in code, so if you have previously defined projects, they still work as usual:
class Filters @Inject()(enabledFilters: EnabledFilters, corsFilter: CORSFilter)
extends DefaultHttpFilters(enabledFilters.filters :+ corsFilter: _*)
§Testing Default Filters
Because there are several filters enabled, functional tests may need to change slightly to ensure that all the tests pass and requests are valid. For example, a request that does not have a Host
HTTP header set to localhost
will not pass the AllowedHostsFilter and will return a 400 Forbidden response instead.
§Testing with AllowedHostsFilter
Because the AllowedHostsFilter filter is added automatically, functional tests need to have the Host HTTP header added.
If you are using FakeRequest
or Helpers.fakeRequest
, then the Host
HTTP header is added for you automatically. If you are using play.mvc.Http.RequestBuilder
, then you may need to add your own line to add the header manually:
RequestBuilder request = new RequestBuilder()
.method(GET)
.header(HeaderNames.HOST, "localhost")
.uri("/xx/Kiwi");
§Testing with CSRFFilter
Because the CSRFFilter filter is added automatically, tests that render a Twirl template that includes CSRF.formField
, i.e.
@(userForm: Form[UserData])(implicit request: RequestHeader, m: Messages)
<h1>user form</h1>
@request.flash.get("success").getOrElse("")
@helper.form(action = routes.UserController.userPost()) {
@helper.CSRF.formField
@helper.inputText(userForm("name"))
@helper.inputText(userForm("age"))
<input type="submit" value="submit"/>
}
must contain a CSRF token in the request. In the Scala API, this is done by importing play.api.test.CSRFTokenHelper._
, which enriches play.api.test.FakeRequest
with the withCSRFToken
method:
import play.api.test.CSRFTokenHelper._
class UserControllerSpec extends PlaySpec with GuiceOneAppPerTest {
"UserController GET" should {
"render the index page from the application" in {
val controller = app.injector.instanceOf[UserController]
val request = FakeRequest().withCSRFToken
val result = controller.userGet().apply(request)
status(result) mustBe OK
contentType(result) mustBe Some("text/html")
}
}
}
In the Java API, this is done by calling CSRFTokenHelper.addCSRFToken
on a play.mvc.Http.RequestBuilder
instance:
requestBuilder = CSRFTokenHelper.addCSRFToken(requestBuilder);
§Disabling Default Filters
The simplest way to disable the default filters is to set the list of filters manually in application.conf
:
play.filters.enabled=[]
This may be useful if you have functional tests that you do not want to go through the default filters.
If you want to remove all filter classes, you can disable it through the disablePlugins
mechanism:
lazy val root = project.in(file(".")).enablePlugins(PlayScala).disablePlugins(PlayFilters)
or by replacing EnabledFilters
:
play.http.filters=play.api.http.NoHttpFilters
If you are writing functional tests involving GuiceApplicationBuilder
and you want to disable default filters, then you can disable all or some of the filters through configuration by using configure
:
GuiceApplicationBuilder().configure("play.http.filters" -> "play.api.http.NoHttpFilters")
§Compile Time Default Filters
If you are using compile time dependency injection, then the default filters are resolved at compile time, rather than through runtime.
This means that the BuiltInComponents
trait now contains an httpFilters
method which is left abstract:
trait BuiltInComponents {
/** A user defined list of filters that is appended to the default filters */
def httpFilters: Seq[EssentialFilter]
}
The default list of filters is defined in play.filters.HttpFiltersComponents
:
trait HttpFiltersComponents
extends CSRFComponents
with SecurityHeadersComponents
with AllowedHostsComponents {
def httpFilters: Seq[EssentialFilter] = Seq(csrfFilter, securityHeadersFilter, allowedHostsFilter)
}
In most cases you will want to mixin HttpFiltersComponents and append your own filters:
class MyComponents(context: ApplicationLoader.Context)
extends BuiltInComponentsFromContext(context)
with play.filters.HttpFiltersComponents {
lazy val loggingFilter = new LoggingFilter()
override def httpFilters = {
super.httpFilters :+ loggingFilter
}
}
If you want to filter elements out of the list, you can do the following:
class MyComponents(context: ApplicationLoader.Context)
extends BuiltInComponentsFromContext(context)
with play.filters.HttpFiltersComponents {
override def httpFilters = {
super.httpFilters.filterNot(_.getClass == classOf[CSRFFilter])
}
}
§Disabling Compile Time Default Filters
To disable the default filters, mixin play.api.NoHttpFiltersComponents
:
class MyComponents(context: ApplicationLoader.Context)
extends BuiltInComponentsFromContext(context)
with NoHttpFiltersComponents
with AssetsComponents {
lazy val homeController = new HomeController(controllerComponents)
lazy val router = new Routes(httpErrorHandler, homeController, assets)
}
§JWT Support
Play’s cookie encoding has been switched to use JSON Web Token (JWT) under the hood. JWT comes with a number of advantages, notably automatic signing with HMAC-SHA-256, and support for automatic “not before” and “expires after” date checks which ensure the session cookie cannot be reused outside of a given time window.
More information is available under Configuring the Session Cookie page.
§Fallback Cookie Support
Play’s cookie encoding uses a “fallback” cookie encoding mechanism that reads in JWT encoded cookies, then attempts reading a URL encoded cookie if the JWT parsing fails, so you can safely migrate existing session cookies to JWT. This functionality is in the FallbackCookieDataCodec
trait and leveraged by DefaultSessionCookieBaker
and DefaultFlashCookieBaker
.
§Legacy Support
Using JWT encoded cookies should be seamless, but if you want, you can revert back to URL encoded cookie encoding by switching to play.api.mvc.LegacyCookiesModule
in application.conf file:
play.modules.disabled+="play.api.mvc.CookiesModule"
play.modules.enabled+="play.api.mvc.LegacyCookiesModule"
§Custom CookieBakers
If you have custom cookies being used in Play, using the CookieBaker[T]
trait, then you will need to specify what kind of encoding you want for your custom cookie baker.
The encode
and decode
methods that Map[String, String]
to and from the format found in the browser have been extracted into CookieDataCodec
. There are three implementations: FallbackCookieDataCodec
, JWTCookieDataCodec
, or UrlEncodedCookieDataCodec
, which respectively represent URL-encoded with an HMAC, or a JWT, or a “read signed or JWT, write JWT” codec.
You will also need to provide a JWTConfiguration
case class, using the JWTConfigurationParser
with the path to your configuration, or use JWTConfiguration()
for the defaults.
@Singleton
class UserInfoCookieBaker @Inject()(service: UserInfoService,
val secretConfiguration: SecretConfiguration)
extends CookieBaker[UserInfo] with JWTCookieDataCodec {
override val COOKIE_NAME: String = "userInfo"
override val isSigned = true
override def emptyCookie: UserInfo = new UserInfo()
override protected def serialize(userInfo: UserInfo): Map[String, String] = service.encrypt(userInfo)
override protected def deserialize(data: Map[String, String]): UserInfo = service.decrypt(data)
override val path: String = "/"
override val jwtConfiguration: JWTConfiguration = JWTConfigurationParser()
}
§Deprecated Futures methods
The following play.libs.concurrent.Futures
static methods have been deprecated:
timeout(A value, long amount, TimeUnit unit)
timeout(final long delay, final TimeUnit unit)
delayed(Supplier<A> supplier, long delay, TimeUnit unit, Executor executor)
A dependency injected instance of Futures
should be used instead:
class MyClass {
@Inject
public MyClass(play.libs.concurrent.Futures futures) {
this.futures = futures;
}
CompletionStage<Double> callWithOneSecondTimeout() {
return futures.timeout(computePIAsynchronously(), Duration.ofSeconds(1));
}
}
§Updated libraries
§Netty 4.1
Netty was upgraded to version 4.1. This was possible mainly because version 4.0 was shaded by play-ws migration to a standalone module. So, if you are using Netty Server and some library that depends on Netty 4.0, we recommend that you try to upgrade to a newer version of the library, or you can start to use the Akka Server.
And if you are, for some reason, directly using Netty classes, you should adapt your code to this new version.
§FluentLenium and Selenium
The FluentLenium library was updated to version 3.2.0 and Selenium was updated to version 3.3.1 (you may want to see the changelog here). If you were using Selenium’s WebDriver API before, there should not be anything to do. Please check this announcement for further information.
If you were using the FluentLenium library you might have to change some syntax to get your tests working again. Please see FluentLenium’s Migration Guide for more details about how to adapt your code.
§HikariCP
HikariCP was updated and a new configuration was introduced: initializationFailTimeout
. This new configuration should be used to replace initializationFailFast
which is now deprecated. See HikariCP changelog and documentation for initializationFailTimeout
to better understand how to use this new configuration.
§Other Configuration changes
There are some configuration changes. 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 |
---|---|
play.crypto.secret |
play.http.secret.key |
play.crypto.provider |
play.http.secret.provider |
play.websocket.buffer.limit |
play.server.websocket.frame.maxLength |
Next: Messages Migration