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

§Play 2.3 Migration Guide

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


In Play 2.3 the play command has become the activator command. Play has been updated to use Activator.

§Activator command

All the features that were available with the play command are still available with the activator command.

The new activator command and the old play command are both wrappers around sbt. If you prefer, you can use the sbt command directly. However, if you use sbt you will miss out on several Activator features, such as templates (activator new) and the web user interface (activator ui). Both sbt and Activator support all the usual console commands such as test and run.

§Activator distribution

Play is distributed as an Activator distribution that contains all Play’s dependencies. You can download this distribution from the Play download page.

If you prefer, you can also download a minimal (1MB) version of Activator from the Activator site. Look for the “mini” distribution on the download page. The minimal version of Activator will only download dependencies when they’re needed.

Since Activator is a wrapper around sbt, you can also download and use sbt directly, if you prefer.

§Build changes


Play uses sbt 0.13.5. If you’re updating an existing project, change your project/ file to:


§Plugin changes

Change the version of the Play plugin in project/plugins.sbt:

addSbtPlugin("" % "sbt-plugin" % "2.3.XXX")

Where 2.3.XXX is the version of Play you want to use.

You will also need to add some sbt-web plugins, see the sbt-web section below.

§Auto Plugins and plugin settings

sbt 0.13.5 brings a new feature named “auto plugins”.

Auto plugins permit sbt plugins to be declared in the project folder (typically the plugins.sbt) as before. What has changed though is that plugins may now declare their requirements of other plugins and what triggers their enablement for a given build. Before auto plugins, plugins added to the build were always available; now plugins are enabled selectively for given modules.

What this means for you is that declaring addSbtPlugin may not be sufficient for plugins that now utilize to the auto plugin functionality. This is a good thing. You may now be selective as to which modules of your project should have which plugins e.g.:

lazy val root = (project in file(".")).enablePlugins(SbtWeb)

The above example shows SbtWeb being added to the root project of a build. In the case of SbtWeb there are other plugins that become enabled if it is e.g. if you also had added the sbt-less-plugin via addSbtPlugin then it will become enabled just because SbtWeb has been enabled. SbtWeb is thus a “root” plugin for that category of plugins.

Play itself is now added using the auto plugin mechanism. The mechanism used in Play 2.2 where playJavaSettings and playScalaSettings were used has been removed. You now use one of the following instead:

lazy val root = (project in file(".")).enablePlugins(PlayJava)


lazy val root = (project in file(".")).enablePlugins(PlayScala)

If you were previously using play.Project, for example a Scala project:

object ApplicationBuild extends Build {

  val appName = "myproject"
  val appVersion = "1.0-SNAPSHOT"

  val appDependencies = Seq()

  val main = play.Project(appName, appVersion, appDependencies).settings(


…then you can continue to use a similar approach via native sbt:

object ApplicationBuild extends Build {

  val appName = "myproject"
  val appVersion = "1.0-SNAPSHOT"

  val appDependencies = Seq()

  val main = Project(appName, file(".")).enablePlugins(play.PlayScala).settings(
    version := appVersion,
    libraryDependencies ++= appDependencies


By moving to the above style settings are now automatically imported when a plugin is enabled.

The keys provided by Play must now also be referenced within the PlayKeys object. For example to reference playVersion you must do so either by importing:

import PlayKeys._

or qualifying it with PlayKeys.playVersion.

Outside of using a .sbt file i.e. if you’re using Scala to describe your build then you may do the following to have PlayKeys within scope:

import play.Play.autoImport._
import PlayKeys._

§Explicit scalaVersion

Play 2.3 supports both Scala 2.11 and Scala 2.10. The Play plugin previously set the scalaVersion sbt setting for you. Now you should indicate which version of Scala you wish to use.

Update your build.sbt or Build.scala to include the Scala version:

For Scala 2.11:

scalaVersion := "2.11.1"

For Scala 2.10:

scalaVersion := "2.10.4"


The largest new feature for Play 2.3 is the introduction of sbt-web. In summary sbt-web allows Html, CSS and JavaScript functionality to be factored out of Play’s core into a family of pure sbt plugins. There are two major advantages to you:

There are other advantages including the fact that sbt-web plugins are able to run within the JVM via Trireme, or natively using Node.js. Note that sbt-web is a development environment and does not participate in the execution of a Play application. Trireme is used by default, but if you have Node.js installed and want blistering performance for your builds then you can provide a system property via sbt’s SBT_OPTS environment variable. For example:

export SBT_OPTS="$SBT_OPTS -Dsbt.jse.engineType=Node"

A feature of sbt-web is that it is not concerned whether you use “javascripts” or “stylesheets” as your folder names. Any files with the appropriate filename extensions are filtered from within the app/assets folder.

A nuance with sbt-web is that all assets are served from the public folder. Therefore if you previously had assets reside outside of the public folder i.e. you used the playAssetsDirectories setting as per the following example:

playAssetsDirectories <+= baseDirectory / "foo"

…then you should now use the following:

unmanagedResourceDirectories in Assets += baseDirectory.value / "foo"

…however note that the files there will be aggregated into the target public folder. This means that a file at “public/a.js” will be overwritten with the file at “foo/a.js”. Alternatively use sub folders off your project’s public folder in order to namespace them.

The following lists all sbt-web related components and their versions at the time of releasing Play 2.3.


"com.typesafe" %% "webdriver" % "1.0.0"
"com.typesafe" %% "jse" % "1.0.0"
"com.typesafe" %% "npm" % "1.0.0"

§sbt plugins

"com.typesafe.sbt" % "sbt-web" % "1.0.0"
"com.typesafe.sbt" % "sbt-webdriver" % "1.0.0"
"com.typesafe.sbt" % "sbt-js-engine" % "1.0.0"

"com.typesafe.sbt" % "sbt-coffeescript" % "1.0.0"
"com.typesafe.sbt" % "sbt-digest" % "1.0.0"
"com.typesafe.sbt" % "sbt-gzip" % "1.0.0"
"com.typesafe.sbt" % "sbt-less" % "1.0.0"
"com.typesafe.sbt" % "sbt-jshint" % "1.0.0"
"com.typesafe.sbt" % "sbt-mocha" % "1.0.0"
"com.typesafe.sbt" % "sbt-rjs" % "1.0.1"


WebJars now play an important role in the provision of assets to a Play application. For example you can declare that you will be using the popular Bootstrap library simply by adding the following dependency in your build file:

libraryDependencies += "org.webjars" % "bootstrap" % "3.2.0"

WebJars are automatically extracted into a lib folder relative to your public assets for convenience. For example if you declared a dependency on RequireJs then you can reference it from a view using a line like:

<script data-main=""javascripts/main.js")" type="text/javascript" src=""lib/requirejs/require.js")"></script>

Note the lib/requirejs/require.js path. The lib folder denotes the extract WebJar assets, the requirejs folder corresponds to the WebJar artifactId, and the require.js refers to the required asset at the root of the WebJar.


npm can be used as well as WebJars by declaring a package.json file in the root of your project. Assets from npm packages are extracted into the same lib folder as WebJars so that, from a code perspective, there is no concern whether the asset is sourced from a WebJar or from an npm package.

From your perspective we aim to offer feature parity with previous releases of Play. While things have changed significantly under the hood the transition for you should be minor. The remainder of this section looks at each part of Play that has been replaced with sbt-web and describes what should be changed.


You must now declare the plugin, typically in your plugins.sbt file:

addSbtPlugin("com.typesafe.sbt" % "sbt-coffeescript" % "1.0.0")

Coffeescript options have changed. The new options are:

CoffeeScriptKeys.sourceMaps := true

CoffeeScriptKeys.bare := false

For more information please consult the plugin’s documentation.


You must now declare the plugin, typically in your plugins.sbt file:

addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.0")

Entry points are now declared using a filter. For example, to declare that foo.less and bar.less are required:

includeFilter in (Assets, LessKeys.less) := "foo.less" | "bar.less"

If you previously used the behavior where files with an underscore preceding the less filename were ignored but all other less files were compiled then use the following filters:

includeFilter in (Assets, LessKeys.less) := "*.less"

excludeFilter in (Assets, LessKeys.less) := "_*.less"

Unlike Play 2.2, the sbt-less plugin allows any user to download the original LESS source file and generated source maps. It allows easier debugging in modern web browsers. This feature is enabled even in production mode.

The plugin’s options are:

Option Description
cleancss Compress output using clean-css.
cleancssOptions Pass an option to clean css, using CLI arguments from .
color Whether LESS output should be colorised
compress Compress output by removing some whitespaces.
ieCompat Do IE compatibility checks.
insecure Allow imports from insecure https hosts.
maxLineLen Maximum line length.
optimization Set the parser’s optimization level.
relativeUrls Re-write relative urls to the base less file.
rootpath Set rootpath for url rewriting in relative imports and urls.
silent Suppress output of error messages.
sourceMap Outputs a v3 sourcemap.
sourceMapFileInline Whether the source map should be embedded in the output file
sourceMapLessInline Whether to embed the less code in the source map
sourceMapRootpath Adds this path onto the sourcemap filename and less file paths.
strictImports Whether imports should be strict.
strictMath Requires brackets. This option may default to true and be removed in future.
strictUnits Whether all unit should be strict, or if mixed units are allowed.
verbose Be verbose.

For more information please consult the plugin’s documentation.

§Closure Compiler

The Closure Compiler has been replaced. Its two important functions of validating JavaScript and minifying it have been factored out into JSHint and UglifyJS 2 respectively.

To use JSHint you must declare it, typically in your plugins.sbt file:

addSbtPlugin("com.typesafe.sbt" % "sbt-jshint" % "1.0.0")

Options can be specified in accordance with the JSHint website and they share the same set of defaults. To set an option you can provide a .jshintrc file within your project’s base directory. If there is no such file then a .jshintrc file will be searched for in your home directory. This behaviour can be overridden by using a JshintKeys.config setting for the plugin.
JshintKeys.config is used to specify the location of a configuration file.

For more information please consult the plugin’s documentation.

UglifyJS 2 is presently provided via the RequireJS plugin (described next). The intent in future is to provide a standalone UglifyJS 2 plugin also for situations where RequireJS is not used.


The RequireJS Optimizer (rjs) has been entirely replaced with one that should be a great deal easier to use. The new rjs is part of sbt-web’s asset pipeline functionality. Unlike its predecessor which was invoked on every build, the new one is invoked only when producing a distribution via Play’s stage or dist tasks.

To use rjs you must declare it, typically in your plugins.sbt file:

addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.1")

To add the plugin to the asset pipeline you can declare it as follows:

pipelineStages := Seq(rjs)

We also recommend that sbt-web’s sbt-digest and sbt-gzip plugins are included in the pipeline. sbt-digest will provide Play’s asset controller with the ability to fingerprint asset names for far-future caching. sbt-gzip produces a gzip of your assets that the asset controller will favor when requested. Your plugins.sbt file for this configuration will then look like:

addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.0.0")

addSbtPlugin("com.typesafe.sbt" % "sbt-gzip" % "1.0.0")

and your pipeline configuration now becomes:

pipelineStages := Seq(rjs, digest, gzip)

The order of stages is significant. You first want to optimize the files, produce digests of them and then produce gzip versions of all resultant assets.

The options for RequireJs optimization have changed entirely. The plugin’s options are:

Option Description
appBuildProfile The project build profile contents.
appDir The top level directory that contains your app js files. In effect, this is the source folder that rjs reads from.
baseUrl The dir relative to the assets or public folder where js files are housed. Will default to “js”, “javascripts” or “.” with the latter if the other two cannot be found.
buildProfile Build profile key -> value settings in addition to the defaults supplied by appBuildProfile. Any settings in here will also replace any defaults.
dir By default, all modules are located relative to this path. In effect this is the target directory for rjs.
generateSourceMaps By default, source maps are generated.
mainConfig By default, ‘main’ is used as the module for configuration.
mainConfigFile The full path to the above.
mainModule By default, ‘main’ is used as the module.
modules The json array of modules.
optimize The name of the optimizer, defaults to uglify2.
paths RequireJS path mappings of module ids to a tuple of the build path and production path. By default all WebJar libraries are made available from a CDN and their mappings can be found here (unless the cdn is set to None).
preserveLicenseComments Whether to preserve comments or not. Defaults to false given source maps (see
webJarCdns CDNs to be used for locating WebJars. By default “org.webjars” is mapped to “jsdelivr”.
webJarModuleIds A sequence of webjar module ids to be used.

For more information please consult the plugin’s documentation.

§Default ivy local repository and cache

Due to Play now using Activator as a launcher, it now uses the default ivy cache and local repository. This means anything previously published to your Play ivy cache that you depend on will need to be published to the local ivy repository in the .ivy2 folder in your home directory.

§Results restructure

In Play 2.2, a number of result types were deprecated, and to facilitate migration to the new results structure, some new types introduced. Play 2.3 finishes this restructuring.

§Scala results

The following deprecated types and helpers from Play 2.1 have been removed:

If you have code that is still using these, please see the Play 2.2 Migration Guide to learn how to migrate to the new results structure.

As planned back in 2.2, 2.3 has renamed play.api.mvc.SimpleResult to play.api.mvc.Result (replacing the existing Result trait). A type alias has been introduced to facilitate migration, so your Play 2.2 code should be source compatible with Play 2.3, however we will eventually remove this type alias so we have deprecated it, and recommend switching to Result.

§Java results

The following deprecated types and helpers from Play 2.1 have been removed:

If you have code that is still using these, please see the Play 2.2 Migration Guide to learn how to migrate to the new results structure.

As planned back in 2.2, 2.3 has renamed play.mvc.SimpleResult to play.mvc.Result. This should be transparent to most Java code. The most prominent places where this will impact is in the error callbacks, and in custom actions.


The template engine is now a separate project, Twirl.

§Content types

The template content types have moved to the twirl package. If the play.mvc.Content type is referenced then it needs to be updated to play.twirl.api.Content. For example, the following code in a Play Java project:

import play.mvc.Content;

Content html = views.html.index.render("42");

will produce the error:

[error] ...: incompatible types
[error] found   : play.twirl.api.Html
[error] required: play.mvc.Content

and requires play.twirl.api.Content to be imported instead.

§sbt settings

The sbt settings for templates are now provided by the sbt-twirl plugin.

Adding additional imports to templates was previously:

templatesImport += ""

and is now:

TwirlKeys.templateImports += ""

Specifying custom template formats was previously:

templatesTypes += ("html" -> "my.HtmlFormat.instance")

and is now:

TwirlKeys.templateFormats += ("html" -> "my.HtmlFormat.instance")

For sbt builds that use the full scala syntax, TwirlKeys can be imported with:

import play.twirl.sbt.Import._

§Html.empty replaced by HtmlFormat.empty

If you were using before Html.empty (play.api.templates.Html.empty), you must now use play.twirl.api.HtmlFormat.empty.

§Play WS

The WS client is now an optional library. If you are using WS in your project then you will need to add the library dependency. For Java projects you will also need to update to a new package.

§Java projects

Add library dependency to build.sbt:

libraryDependencies += javaWs

Update to the new library package in source files:


§Scala projects

Add library dependency to build.sbt:

libraryDependencies += ws

In addition, usage of the WS client now requires a Play application in scope. Typically this is achieved by adding:

import play.api.Play.current

The WS API has changed slightly, and WS.client now returns an instance of WSClient rather than the underlying AsyncHttpClient object. You can get to the AsyncHttpClient by calling WS.client.underlying.


There are various changes included for Anorm in this new release.

For improved type safety, type of query parameter must be visible, so that it can be properly converted. Now using Any as parameter value, explicitly or due to erasure, leads to compilation error No implicit view available from Any => anorm.ParameterValue.

// Wrong
val p: Any = "strAsAny"
SQL("SELECT * FROM test WHERE id={id}").
  on('id -> p) // Erroneous - No conversion Any => ParameterValue

// Right
val p = "strAsString"
SQL("SELECT * FROM test WHERE id={id}").on('id -> p)

// Wrong
val ps = Seq("a", "b", 3) // inferred as Seq[Any]
SQL("SELECT * FROM test WHERE (a={a} AND b={b}) OR c={c}").
  on('a -> ps(0), // ps(0) - No conversion Any => ParameterValue
    'b -> ps(1), 
    'c -> ps(2))

// Right
val ps = Seq[anorm.ParameterValue]("a", "b", 3) // Seq[ParameterValue]
SQL("SELECT * FROM test WHERE (a={a} AND b={b}) OR c={c}").
  on('a -> ps(0), 'b -> ps(1), 'c -> ps(2))

If passing value without safe conversion is required, anorm.Object(anyVal) can be used to set an opaque parameter.

Moreover, erasure issues about parameter value is fixed: type is no longer ParameterValue[_] but simply ParameterValue.

Types for parameter names are also unified (when using .on(...)). Only String and Symbol are now supported as name.

Type Pk[A] has been deprecated. You can still use it as column mapping, but need to explicitly pass either Id[A] or NotAssigned as query parameter (as consequence of type safety improvements):

// Column mapping, deprecated but Ok
val pk: Pk[Long] = SQL("SELECT id FROM test WHERE name={n}").
  on('n -> "mine").as(get[Pk[Long]].single)

// Wrong parameter
val pkParam: Pk[Long] = Id(1l)
val name1 = "Name #1"
SQL"INSERT INTO test(id, name) VALUES($pkParam, $name1)".execute()
// ... pkParam is passed as Pk in query parameter, 
// which is now wrong as a parameter type (won't compile)

// Right parameter Id
val idParam: Id[Long] = Id(2l) // same as pkParam but keep explicit Id type
val name2 = "Name #2"
SQL"INSERT INTO test(id, name) VALUES($idParam, $name2)".execute()

// Right parameter NotAssigned
val name2 = "Name #3"
SQL"INSERT INTO test(id, name) VALUES($NotAssigned, $name2)".execute()

As deprecated Pk[A] is similar to Option[A], which is supported by Anorm in column mapping and as query parameter, it’s preferred to replace Id[A] by Some[A] and NotAssigned by None:

// Column mapping, deprecated but Ok
val pk: Option[Long] = SQL("SELECT id FROM test WHERE name={n}").
  on('n -> "mine").as(get[Option[Long]].single)

// Assigned primary key as parameter
val idParam: Option[Long] = Some(2l)
val name1 = "Id"
SQL"INSERT INTO test(id, name) VALUES($idParam, $name1)".execute()

// Right parameter NotAssigned
val name2 = "NotAssigned"
SQL"INSERT INTO test(id, name) VALUES($None, $name2)".execute()


The in-built Bootstrap field constructor has been deprecated, and will be removed in a future version of Play.

There are a few reasons for this, one is that we have found that Bootstrap changes too drastically between versions and too frequently, such that any in-built support provided by Play quickly becomes stale and incompatible with the current Bootstrap version.

Another reason is that the current Bootstrap requirements for CSS classes can’t be implemented with Play’s field constructor alone, a custom input template is also required.

Our view going forward is that if this is a feature that is valuable to the community, a third party module can be created which provides a separate set of Bootstrap form helper templates, specific to given Bootstrap versions, allowing a much better user experience than can currently be provided.

§Session timeouts

The session timeout configuration item, session.maxAge, used to be an integer, defined to be in seconds. Now it’s a duration, so can be specified with values like 1h or 30m. Unfortunately, the default unit if specified with no time unit is milliseconds, which means a config value of 3600 was previously treated as one hour, but is now treated as 3.6 seconds. You will need to update your configuration to add a time unit.

§Java JUnit superclasses

The Java WithApplication, WithServer and WithBrowser JUnit test superclasses have been modified to define an @Before annotated method. This means, previously where you had to explicitly start a fake application by defining:

public void setUp() {

Now you don’t need to. If you need to provide a custom fake application, you can do so by overriding the provideFakeApplication method:

protected FakeApplication provideFakeApplication() {
    return Helpers.fakeApplication(Helpers.inMemoryDatabase());

§Session and Flash implicits

The Scala Controller provides implicit Session, Flash and Lang parameters, that take an implicit RequestHeader. These exist for convenience, so a template for example can take an implicit argument and they will be automatically provided in the controller. The name of these was changed to avoid conflicts where these parameter names might be shadowed by application local variables with the same name. session became request2Session, flash became flash2Session, lang became lang2Session. Any code that invoked these explicitly consequently will break.

It is not recommended that you invoke these implicit methods explicitly, the session, flash and lang parameters are all available on the RequestHeader, and using the RequestHeader properties makes it much clearer where they come from when reading the code. It is recommended that if you have code that uses the old methods, that you modify it to access the corresponding properties on RequestHeader directly.

Next: Play 2.2 migration guide