documentation/manual/releases/release27/migration27/JavaHttpContextMigration27.md
Http.Context changesplay.mvc.Http.Context is a crucial part of Java HTTP & MVC APIs, but it is not a good abstraction of how these APIs should work. It either has some concepts that could be better modeled or implementation details that are complex to test and to reason about in a multi-threading framework like Play. For example, Http.Context uses a thread local to capture and access the current request, but it gives the impression that the current request can be accessed from any place, which is not currently true if you are using Actors or a custom thread pool.
Regarding the API modeling, there are some duplicated concepts (like play.mvc.Result vs play.mvc.Http.Response) and some methods look out of the place (for example play.mvc.Http.Context.id instead of using play.mvc.Http.RequestHeader.id). Given that, multiple changes were made to Http.Context and the idea is to move away from it. We are then providing new APIs that are simpler to test, to reason about and to maintain in the future.
Since play.mvc.Http.Context is a central part of the existing APIs, deprecating it had an impact on multiple places that were depending on it, take for example play.mvc.Controller. This page documents these changes and how to migrate, but you can see the deprecated Javadocs for each methods too.
Http.Context.current() and Http.Context.request() deprecatedThat means other methods that depend directly on these two were also deprecated:
play.mvc.Controller.ctx()play.mvc.Controller.request()Before Play 2.7, when using Play with Java, the only way to access the Http.Request was Http.Context.current() which was used internally by Controller.request() method. The problem with Http.Context.current() is that it is implemented using a thread local, which is harder to test, to keep in sync with changes made at other places and makes it harder to access the request in other threads.
With Play 2.7 you can now access the current request by just adding it as a param to your routes and actions.
For example, the routes files contain:
GET / controllers.HomeController.index(request: Request)
And the corresponding action method:
import play.mvc.*;
public class HomeController extends Controller {
public Result index(Http.Request request) {
return ok("Hello, your request path " + request.path());
}
}
Play will automatically detect a route param of type Request (which is an import for play.mvc.Http.Request) and will pass the actual request into the corresponding action method's param.
Note: It is unlikely but possible that you have a custom
QueryStringBindableorPathBindablewith the nameRequest. If so, that one would now collide with Play detection of request params. Therefore you should use the fully qualified name of yourRequesttype, for example.GET / controllers.HomeController.index(myRequest: com.mycompany.Request)
If you use Http.Context.current() in other places besides controllers you have to pass the desired data via method parameters to these places now. Look at this example which checks if the current request's remote address is on a blacklist:
import play.mvc.Http;
public class SecurityHelper {
public static boolean isBlacklisted() {
String remoteAddress = Http.Context.current().request().remoteAddress();
return blacklist.contains(remoteAddress);
}
}
Corresponding controller:
import play.mvc.*;
public class HomeController extends Controller {
public Result index() {
if (SecurityHelper.isBlacklisted()) {
return badRequest();
}
return ok("Hello, your request path " + request().path());
}
}
public class SecurityHelper {
public static boolean isBlacklisted(String remoteAddress) {
return blacklist.contains(remoteAddress);
}
}
Corresponding controller:
import play.mvc.*;
public class HomeController extends Controller {
public Result index(Http.Request request) {
if (SecurityHelper.isBlacklisted(request.remoteAddress())) {
return badRequest();
}
return ok("Hello, your request path " + request.path());
}
}
Security.Authenticated changesTo secure action to prevent access without authentication you can use @Security.Authenticated.
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Security;
public class Secured extends Security.Authenticator {
@Override
public String getUsername(Http.Context ctx) {
return ctx.session().get("id");
}
@Override
public Result onUnauthorized(Http.Context ctx) {
ctx.flash().put("danger", "You need to login before access the application.");
return redirect(controllers.routes.HomeController.login());
}
}
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Security;
import java.util.Optional;
public class Secured extends Security.Authenticator {
@Override
public Optional getUsername(Http.Request req) {
return req.session().getOptional("id");
}
@Override
public Result onUnauthorized(Http.Request req) {
return redirect(controllers.routes.HomeController.login()).
flashing("danger", "You need to login before access the application.");
}
}
And the corresponding action method:
@Security.Authenticated(Secured.class)
public Result index(Http.Request request) {
return ok(views.html.index.render(request));
}
Action.call(Context) deprecatedIf you are using [[action composition|JavaActionsComposition]] you have to update your actions to avoid Http.Context.
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import java.util.concurrent.CompletionStage;
public class MyAction extends Action.Simple {
public CompletionStage<Result> call(Http.Context ctx) {
return delegate.call(ctx);
}
}
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import java.util.concurrent.CompletionStage;
public class MyAction extends Action.Simple {
public CompletionStage<Result> call(Http.Request req) {
return delegate.call(req);
}
}
Http.Context.response() and Http.Response class deprecatedThat means other methods that depend directly on these were also deprecated:
play.mvc.Controller.response()Http.Response was deprecated with other accesses methods to it. It was mainly used to add headers and cookies, but these are already available in play.mvc.Result and then the API got a little confused. For Play 2.7, you should migrate code like:
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Controller;
public class FooController extends Controller {
// This uses the deprecated response() APIs
public Result index() {
response().setHeader("Header", "Value");
response().setCookie(Http.Cookie.builder("Cookie", "cookie value").build());
response().discardCookie("CookieName");
return ok("Hello World");
}
}
The code above should be written as:
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Controller;
public class FooController extends Controller {
// This uses the deprecated response() APIs
public Result index() {
return ok("Hello World")
.withHeader("Header", "value")
.withCookies(Http.Cookie.builder("Cookie", "cookie value").build())
.discardCookie("CookieName");
}
}
If you have action composition that depends on Http.Context.response, you can also rewrite it like the code below:
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import java.util.concurrent.CompletionStage;
public class MyAction extends Action.Simple {
@Override
public CompletionStage<Result> call(Http.Context ctx) {
ctx.response().setHeader("Name", "Value");
return delegate.call(ctx);
}
}
The code above should be written as:
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import java.util.concurrent.CompletionStage;
public class MyAction extends Action.Simple {
@Override
public CompletionStage<Result> call(Http.Request req) {
return delegate.call(req)
.thenApply(result -> result.withHeader("Name", "Value"));
}
}
Http.Context deprecatedThe following methods have been deprecated:
Http.Context.lang()Http.Context.changeLang(Lang lang)Http.Context.changeLang(String code)Http.Context.clearLang()Http.Context.setTransientLang(Lang lang)Http.Context.setTransientLang(String code)Http.Context.clearTransientLang()Http.Context.messages()That means other methods that depend directly on these were also deprecated:
play.mvc.Controller.lang()play.mvc.Controller.changeLang(Lang lang)play.mvc.Controller.changeLang(String code)play.mvc.Controller.clearLang()The new way of changing lang now is to have an instance of play.i18n.MessagesApi injected and call corresponding play.mvc.Result methods. For example:
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
import play.i18n.MessagesApi;
public class FooController extends Controller {
public Result action() {
changeLang(Lang.forCode("es"));
return Results.ok("Hello");
}
}
import javax.inject.Inject;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
import play.i18n.MessagesApi;
public class FooController extends Controller {
private final MessagesApi messagesApi;
@Inject
public FooController(MessagesApi messagesApi) {
this.messagesApi = messagesApi;
}
public Result action() {
return Results.ok("Hello").withLang(Lang.forCode("es"), messagesApi);
}
}
If you are using changeLang to change the Lang used to render a template, you should now pass the Messages itself as a parameter. This will make the template clearer and easier to read. For example, in an action method, you have to create a Messages instance like:
import play.mvc.Result;
import play.mvc.Controller;
public class MyController extends Controller {
public Result action() {
changeLang(Lang.forCode("es"));
return ok(myview.render(messages));
}
}
import javax.inject.Inject;
import play.i18n.Messages;
import play.i18n.MessagesApi;
import play.mvc.Result;
import play.mvc.Controller;
public class MyController extends Controller {
private final MessagesApi messagesApi;
@Inject
public MyController(MessagesApi messagesApi) {
this.messagesApi = messagesApi;
}
public Result action() {
Messages messages = this.messagesApi.preferred(Lang.forCode("es"));
return ok(myview.render(messages));
}
}
Or if you want to have a fallback to the languages of the request you can do that as well:
import javax.inject.Inject;
import play.i18n.Messages;
import play.i18n.MessagesApi;
import play.mvc.Result;
import play.mvc.Controller;
public class MyController extends Controller {
private final MessagesApi messagesApi;
@Inject
public MyController(MessagesApi messagesApi) {
this.messagesApi = messagesApi;
}
public Result action() {
Lang lang = Lang.forCode("es");
// Get a Message instance based on the spanish locale, however if that isn't available
// try to choose the best fitting language based on the current request
Messages messages = this.messagesApi.preferred(request.withTransientLang(lang));
return ok(myview.render(messages));
}
}
Note: To not repeat that code again and again inside each action method you could e.g. create the
Messagesinstance in an action of the [[action composition chain|JavaActionsComposition]] and save that instance in a request Attribute so you can access it later.
Now the template:
@()(implicit messages: play.i18n.Messages)
@{messages.at("hello")}
Note: Declaring
messagesasimplicitwill make it available to sub-views that implicitly require aMessagesProviderwithout the need to pass them one.
And the same applies to clearLang:
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
import play.i18n.MessagesApi;
public class FooController extends Controller {
public Result action() {
clearLang();
return Results.ok("Hello");
}
}
import javax.inject.Inject;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
import play.i18n.MessagesApi;
public class FooController extends Controller {
private final MessagesApi messagesApi;
@Inject
public FooController(MessagesApi messagesApi) {
this.messagesApi = messagesApi;
}
public Result action() {
return Results.ok("Hello").withoutLang(messagesApi);
}
}
Http.Context.session() deprecatedThat means other methods that depend directly on it were also deprecated:
play.mvc.Controller.session()play.mvc.Controller.session(String key, String value)play.mvc.Controller.session(String key)The new way to retrieve the session of a request is to call the session() method of an Http.Request instance. The new way to manipulate the session is to call corresponding play.mvc.Result methods. For example:
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
public class FooController extends Controller {
public Result info() {
String user = session("current_user");
return Results.ok("Hello " + user);
}
public Result login() {
session("current_user", "[email protected]");
return Results.ok("Hello");
}
public Result logout() {
session().remove("current_user");
return Results.ok("Hello");
}
public Result clear() {
session().clear();
return Results.ok("Hello");
}
}
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
import java.util.Optional;
public class FooController extends Controller {
public Result info(Http.Request request) {
// Get the current user or then fallback to guest
String user = request.session().getOptional("current_user").orElse("guest");
return Results.ok("Hello " + user);
}
public Result login(Http.Request request) {
return Results.ok("Hello")
.addingToSession(request, "current_user", "[email protected]");
}
public Result logout(Http.Request request) {
return Results.ok("Hello")
.removingFromSession(request, "current_user");
}
public Result clear() {
return Results.ok("Hello")
.withNewSession();
}
}
Http.Context.flash() deprecatedThat means other methods that depend directly on it were also deprecated:
play.mvc.Controller.flash()play.mvc.Controller.flash(String key, String value)play.mvc.Controller.flash(String key)The new way to retrieve the flash of a request is to call the flash() method of a Http.Request instance. The new way to manipulate the flash is to call corresponding play.mvc.Result methods. For example:
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
public class FooController extends Controller {
public Result info() {
String message = flash("message");
return Results.ok("Message: " + message);
}
public Result login() {
flash("message", "Login successful");
return Results.redirect("/dashboard");
}
public Result logout() {
flash().remove("message");
return Results.redirect("/");
}
public Result clear() {
flash().clear();
return Results.redirect("/");
}
}
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
public class FooController extends Controller {
public Result info(Http.Request request) {
String message = request.flash().getOptional("message").orElse("The default message");
return Results.ok("Message: " + message);
}
public Result login() {
return Results.redirect("/dashboard")
.flashing("message", "Login successful");
}
public Result logout() {
return Results.redirect("/")
.removingFromFlash("message");
}
public Result clear() {
return Results.redirect("/")
.withNewFlash();
}
}
Inside templates, Play offered you various helper methods which rely on Http.Context internally. These methods are deprecated starting with Play 2.7. Instead, you have to explicitly pass the desired object to your templates now.
@()
@ctx()
@request()
@response()
@flash()
@session()
@lang()
@messages()
@Messages("some_msg_key")
@(Http.Request request, Lang lang, Messages messages)
@request
@request.flash()
@request.session()
@lang
@messages
@messages("some_msg_key")
There is no direct replacement for ctx() and response().
Request, Messages or Lang instanceSome template tags need to access a Http.Request, Messages or Lang instance in order to work correctly. Until now these tags just made use of Http.Context.current() to retrieve such instances.
Because Http.Context is deprecated however, such instances should now be passed as implicit parameters to templates which make use of such tags. By marking the parameter as implicit you don't always have to pass it on to the tag which actually needs it, but the tag can retrieve it from the implicit scope automatically.
Note: To better understand how implicit parameters works, see implicit parameter and where does Scala look for implicits sections of Scala FAQ.
Following tags need an implicit Request instance to be present:
@(arg1, arg2,...)(implicit request: Http.Request)
@*These tags will automatically use the implicit request passed to this template:*@
@helper.jsloader
@helper.script
@helper.style
@helper.javascriptRouter
@CSRF
@CSRF.formField
@CSRF.getToken
@defaultpages.devError
@defaultpages.devNotFound
@defaultpages.error
@defaultpages.badRequest
@defaultpages.notFound
@defaultpages.todo
@defaultpages.unauthorized
So, if you have a view that use some of the tags above, for example if you have a file app/views/names.scala.html like below:
@(names: List[String])(implicit request: Http.Request)
<html>
<head>
<!-- `scripts` tags requires a request to generate a CSP nonce -->
@script(args = 'type -> "text/javascript") {
alert("Just a single inline script");
}
</head>
<body>
...
</body>
</html>
Your controller will need to pass the request as a parameter to the render method:
import java.util.List;
import java.util.ArrayList;
import javax.inject.Inject;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Controller;
public class SomeController extends Controller {
public Result action(Http.Request request) {
List<String> names = new ArrayList<>("Jane", "James", "Rich");
return ok(views.html.names.render(names, request));
}
}
There are also the helper tags that need an implicit Messages instance to be present:
@(arg1, arg2,...)(implicit messages: play.i18n.Messages)
These tags will automatically use the implicit messages passed to this template:
@helper.inputText
@helper.inputDate
@helper.inputCheckboxGroup
@helper.inputFile
@helper.inputRadioGroup
@helper.inputPassword
@helper.textarea
@helper.input
@helper.select
@helper.checkbox
So, if you have a view that use some of the tags above, for example if you have a file app/views/userForm.scala.html like below:
@(userForm: Form[User])(implicit messages: play.i18n.Messages)
<html>
<head>
<title>User form page</title>
</head>
<body>
@helper.form(action = routes.UsersController.save) {
@helper.inputText(userForm("name"))
@helper.inputText(userForm("email"))
...
})
</body>
</html>
Your controller will then be like:
import javax.inject.Inject;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Controller;
import play.i18n.Messages;
import play.i18n.MessagesApi;
import play.data.FormFactory;
public class SomeController extends Controller {
private final FormFactory formFactory;
private final MessagesApi messagesApi;
@Inject
public SomeController(FormFactory formFactory, MessagesApi messagesApi) {
this.formFactory = formFactory;
this.messagesApi = messagesApi;
}
public Result action(Http.Request request) {
Form<User> userForm = formFactory.form(User.class);
// Messages instance that will be passed to render the view and
// inside the view will be passed implicitly to helper tags.
Messages messages = messagesApi.preferred(request);
return ok(views.html.userForm.render(userForm, messages));
}
}
Note: some of these features were previously provided by
PlayMagicForJavaand were heavily depending onHttp.Context.current(). That is why you will see warnings like:method implicitXXX in object PlayMagicForJava is deprecated (since 2.7.0): See https://www.playframework.com/documentation/latest/JavaHttpContextMigration27Passing the parameters to your view will make it more clear what is happening and where your view is depending on other data.
Play itself does not provide tags that need a Lang instance to be present, third-party modules however may do:
@(arg1, arg2,...)(implicit lang: play.i18n.Lang)
Third-party tags will automatically use the implicit messages passed to this template. You can pass an implicit instance of Lang to your view just like the examples for Http.Request and Messages above.
Http.ContextWhen retrieving the Field of a Form (e.g. via myform.field("username") or just myform("username") inside templates) the language of the current Http.Context was used to format the value of the field. Starting with Play 2.7 however this isn't the case anymore. Instead you can now explicitly set the language the form should use when retrieving a field. To make things simple and to not force you to set the language for every form explicitly, Play sets it during binding already:
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Controller;
import play.data.Form;
import play.data.FormFactory;
public class MyController extends Controller {
private final FormFactory formFactory;
@Inject
public MyController(FormFactory formFactory) {
this.formFactory = formFactory;
}
public Result action(Http.Request request) {
// In this example, the language of the form will be set
// to the preferred language of the request.
Form<User> form = formFactory.form(User.class).bindFromRequest(request);
return ok(views.form.render(form));
}
}
You can also change the language of an existing form if you need:
import play.mvc.Result;
import play.mvc.Controller;
import play.i18n.Lang;
import play.data.Form;
import play.data.FormFactory;
public class MyController extends Controller {
private final FormFactory formFactory;
@Inject
public MyController(FormFactory formFactory) {
this.formFactory = formFactory;
}
public Result action(Http.Request request) {
// There is first the lang from request
Form<User> form = formFactory.form(User.class).bindFromRequest(request);
// Let's change the language to `es`.
Lang lang = Lang.forCode("es");
Form<User> formWithNewLang = form.withLang(lang);
return ok(views.form.render(formWithNewLang));
}
}
Note: changing the language of the the current
Http.Context(e.g. viaHttp.Context.current().changeLang(...)orHttp.Context.current().setTransientLang(...)) does not have an effect on the language used to retrieve the field value of a form anymore - as explained, useform.withLang(...)instead.
Http.Context Request tags removed from argsRequest tags, which [[have been deprecated|Migration26#Request-tags-deprecation]] in Play 2.6, have finally been removed in Play 2.7. Therefore the args map of an Http.Context instance no longer contains these removed request tags as well. Instead you can use the request.attrs() method now, which provides you the same request attributes.
argsThe @AddCSRFToken action annotation added two entries named CSRF_TOKEN and CSRF_TOKEN_NAME to the args map of an Http.Context instance. These entries have been removed. Use [[the new correct way to get the token|JavaCsrf#Getting-the-current-token]].
Until Play 2.6, when using Java [[routing DSL|JavaRoutingDsl]], there is no other way to access the current request besides Http.Context.current(). Now the DSL has new methods where a request will be passed to the block.
import play.mvc.Http;
import play.routing.Router;
import play.routing.RoutingDsl;
import javax.inject.Inject;
import static play.mvc.Results.ok;
public class MyRouter {
private final RoutingDsl routingDsl;
@Inject
public MyRouter(RoutingDsl routingDsl) {
this.routingDsl = routingDsl;
}
public Router router() {
return routingDsl
.GET("/hello/:to")
.routeTo(to -> {
Http.Request request = Http.Context.current().request();
return ok("Hello " + to + ". Here is the request path: " + request.path());
})
.build();
}
}
In the example above, we need to use Http.Context.current() to access the request. From now on, you can instead write the code like below:
import play.routing.Router;
import play.routing.RoutingDsl;
import javax.inject.Inject;
import static play.mvc.Results.ok;
public class MyRouter {
private final RoutingDsl routingDsl;
@Inject
public MyRouter(RoutingDsl routingDsl) {
this.routingDsl = routingDsl;
}
public Router router() {
return routingDsl
.GET("/hello/:to")
.routingTo((request, to) ->
ok("Hello " + to + ". Here is the request path: " + request.path())
)
.build();
}
}
An important aspect to note is that, in the new API, Http.Request will always be the first parameter for the function blocks.
Http.Context and JPA thread localIf you followed the above migration notes and changed all your code so it doesn't make use of API's that rely on Http.Context (meaning you don't get compiler warnings anymore) you can disable the Http.Context thread local.
Just add the following line to your application.conf file:
play.allowHttpContext = false
To also disable the play.db.jpa.JPAEntityManagerContext thread local add:
play.jpa.allowJPAEntityManagerContext = false