The Master Class of "Learn Spring Security" is live:
1. Overview
In this article, we will explore the aspect of routing in developing web applications with the Play Framework.
Routing is a common concept that appears in most web development frameworks including Spring MVC.
A route is a URL pattern that is mapped to a handler. The handler can be a physical file, such as a downloadable asset in the web application or a class that processes the request, such as a controller in an MVC application.
2. Setup
First, we’ll need to create a Java Play application. The details of how to set up Play Framework on your machine are available in my introductory article. You can read it by following this link.
By the end of the setup, we should be having a working Play application that we are able to access from a browser.
3. HTTP Routing
You may be wondering how Play knows which controller to consult whenever we send an HTTP request. The answer lies in the webapp/conf/routes configuration file.
Play’s router translates HTTP requests into action calls. HTTP requests are considered to be events in MVC architecture and the router reacts to them by consulting the routes file for which controller and which action in that controller to execute.
Each of these events supplies a router with two parameters, i.e. a request path with its query String and a request’s HTTP method.
4. Basic Routing With Play
For the router to do its work, the conf/routes file must define mappings of HTTP methods and URI patterns to appropriate controller actions:
GET / controllers.HomeController.index GET / count controllers.CountController.count GET / message controllers.AsyncController.message GET / assets/*file controllers.Assets.versioned(path="/public", file: Asset)
All routes files must also map the static resources in the webapp/public folder available to the client on the /assets endpoint.
Notice the syntax of defining HTTP routes, and HTTP method space URI pattern space controller action.
5. URI Patterns
In this section, we will expound a little on URI patterns.
5.1. Static URI patterns
The first three URI patterns above are static. This means that mapping of the URLs to resources occurs without any further processing in the controller actions.
As long as a controller method is called, it returns a static resource whose content is determined before the request.
5.2. Dynamic URI patterns
The last of the above URI patterns is dynamic. This means that the controller action servicing a request on these URIs needs some information from the request to determine the response. In the above case, it expects a file name.
The normal sequence of events is that the router receives an event, picks the path from the URL, decodes its segments and passes them to the controller.
This is the point where path and query parameters are injected into the controller action as parameters. We will demonstrate this with an example in the next sections.
6. Advanced Routing With Play
In this section, we will be discussing advanced options in routing using Dynamic URI patterns in detail.
6.1. Simple Path Parameters
These are unnamed parameters in a request URL that appear after the host and port and are parsed in order of appearance.
Inside webapp/app/HomeController.java, let us create a new action like so:
public Result greet(String name) { return ok("Hello " + name); }
We want to be able to pick a path parameter from the request URL and map it to the variable name. Remember path parameters are not named in the URL.
The router will get those values from a route configuration.
So, let’s open webapp/conf/routes and create a mapping for this new action:
GET /greet/:name controllers.HomeController.greet(name)
Notice how we inform a router that name is a dynamic path segment with the colon syntax and then go ahead to pass it as a parameter to the greet action call.
Now load http://locahost:9000/greet/john in the browser and you will be greeted by name:
Hello john
It so happens that if our action parameter is of string type, we may pass it during the action call without specifying the parameter type, this is not the same for other types.
Let’s spice up our /greet endpoint with age information.
Back to HomeController‘s greet action, change it to:
public Result greet(String name,int age) { return ok("Hello " + name + ", you are " + age + " years old"); }
And the route to:
GET /greet/:name/:age controllers.HomeController.greet(name,age:Integer)
Notice how we specify age parameter type in the action call on the far right, because it’s not a String.
Notice also the Scala syntax for declaring a variable, age:Integer. In Java, we would use Integer age syntax. Play Framework is built in Scala, so there is Scala syntax in a lot of places.
Let’s load http://localhost:9000/greet/john/26:
Hello john, you are 26 years old
6.2. Wildcards in Path Parameters
In our routes configuration file, the last mapping is:
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
We use a wildcard in the dynamic part of the path. What we are telling Play is that whatever value replaces *file in the actual request should be parsed as a whole and not decoded like in other cases of path parameters.
In this example, the controller is an inbuilt one, Assets, which allows the client to download files from the webapp/public folder. When we load http://localhost:9000/assets/images/favicon.png, we should see the image of the Play favicon in the browser since it’s present in the /public/images folder.
Let’s create our own example action in HomeController.java:
public Result introduceMe(String data) { String[] clientData=data.split(","); return ok("Your name is "+clientData[0]+", you are "+clientData[1]+" years old"); }
Notice that in this action, we receive one String parameter and apply our own logic to decode it. In this case, the logic is to split a comma delimited String into an array. Previously, we depended on a router to decode this data for us.
With wildcards, we are on our own. We are hoping that the client gets our syntax correct while passing this data in. Ideally, we should validate the incoming String before using it.
Let’s create a route to this action:
GET /*data controllers.HomeController.introduceMe(data)
Now load the URL http://localhost:9000/john,26. This will print:
Your name is john, you are 26 years old
6.3. Regex in Path Parameters
Just like wildcards, we can use regular expressions for the dynamic part. Let’s add an action that receives a number and returns its square:
Let’s add an action that receives a number and returns its square:
public Result squareMe(Long num) { return ok(num + " Squared is " + (num*num)); }
Let’s add its route:
GET /square/$num<[0-9]+> controllers.HomeController.squareMe(num:Long)
Let’s place this route below the introduceMe route to introduce a new concept. With this routing configuration, only URLs where the regex part is a positive integer will be routed to squareMe action.
Now if you placed the route as instructed in the previous paragraph, load http://localhost:9000/square/2. You should be greeted with an ArrayIndexOutOfBoundsException:
If we check the error logs in the server console, we will realize that the action call was actually performed on introduceMe action rather than squareMe action. As said earlier about wildcards, we are on our own and we did not validate incoming data.
Therefore introduceMe was called with a single string “2” instead of a comma delimited String. After splitting it, we got an array with only one element and yet tried to access an element at index 1.
But why call introduceMe yet we routed this call to squareMe? The reason is a Play feature we will cover next called Routing Priority.
7. Routing Priority
If there is a conflict between routes as there is between squareMe and introduceMe, then the first route in declaration order is taken.
Why is there a conflict? Because of the wildcard context path /*data matches any request URL apart from the base path /. So every route whose URI pattern uses wildcards should appear last in order.
Now change the declaration order of the routes such that the introduceMe route comes after squareMe. Load:
2 Squared is 4
To test the power of regular expressions in a route, try loading http://locahost:9000/square/-1, a router will fail to match the squareMe route and match introduceMe route and we will get the ArrayIndexOutOfBoundsException again.
This is because -1 does not get matched by the provided regular expression, neither does any alphabetic character.
8. Parameters
Up until this point, the only aspect we’ve covered about parameters is how to declare their type in the routes file.
In this section, we will look at more options available to us when dealing with parameters in routes.
8.1. Parameters With Fixed Values
Sometimes we will want to use a fixed value for a parameter. This is our way of telling Play to use the path parameter provided or if the request context is path /, then use a certain fixed value.
Another way of looking at it is having two endpoints or context paths leading to the same controller action. One endpoint requiring a parameter from the request URL and defaulting to the other in case the said parameter is absent.
To demonstrate this, let’s make a change to HomeController.index() action like so:
public Result index() { return ok("Routing in Play by Baeldung"); }
Assuming we don’t always want our API to return a String:
Routing in Play by Baeldung
We want to control it by sending the name of an author of the article along with the request, defaulting to the fixed value Baeldung only if the request does not have the author parameter.
So let’s further change the index action by adding a parameter:
public Result index(String author) { return ok("REST API with Play by "+author); }
Let’s also see how to add a fixed value parameter to the route:
GET / controllers.HomeController.index(author="Baeldung") GET /:author controllers.HomeController.index(author)
Notice how we now have two separate routes all leading to the HomeController.index action instead of one.
When we now load http://localhost:9000/ from the browser we get:
Routing in Play by Baeldung
While when we load http://localhost:9000/john, we get:
Routing in Play by john
8.2. Parameters with Default Values
Apart from having fixed values, parameters can also have what we call default values. Both provide fallback values to the controller action parameters in case the request does not provide the required values.
The difference between the two is that fixed values are used as a fallback for path parameters while default values are used as a fallback for query parameters.
Path parameters as in http://localhost:9000/param1/param2 and query parameters as in http://localhost:9000/?param1=value1¶m2=value2.
The second difference is in the syntax of declaring the two in a route. Fixed value parameters use the assignment operator as in:
author="Baeldung"
While default values use a different type of assignment:
author ?= "Baeldung"
We use the ?= operator which conditionally assigns Baeldung to author in case author is found to contain no value.
To have a complete demonstration, let’s get back to HomeController.index action. Let’s say, apart from the author name which is a path parameter, we also want to pass author id as a query parameter which should default to 1 if not passed in the request.
We will change index action to:
public Result index(String author,int id) { return ok("Routing in Play by:"+author+" ID:"+id); }
and the index routes to:
GET / controllers.HomeController.index(author="Baeldung",id:Int?=1) GET /:author controllers.HomeController.index(author,id:Int?=1)
Now loading http://localhost:9000/:
Routing in Play by:Baeldung ID:1
Hitting http://localhost:9000/?id=10:
Routing in Play by:Baeldung ID:10
What about http://localhost:9000/john:
Routing in Play by:john ID:1
and http://localhost:9000/john?id=5:
Routing in Play by:john ID:5
9. Conclusion
In this article, we explored the notion of Routing in Play applications. You can also check out my other article, REST API with Play Framework by following this link.
The source code for the complete project is available on GitHub.