Path variables are an essential component which we use when interacting with REST APIs. In this tutorial, we will discuss what path variables are (not to be confused with query parameters) and how to use and configure them in Spring controllers.
If you are just starting out with building REST APIs in Spring or Spring Boot then I recommend that you also check out our article regarding how to build a CRUD REST API in Spring Boot, as it covers the basics for Spring Boot beginners.
What are path variables
Path variables are passed on as part of the universal resource identifier (the URI or URL) and are used to indicate which entity, document or sub-document you intend to access during your API call. Path variables are part of the “path” to the document and is not the same as a query parameter, which is used to filter or narrow down incoming results.
When interacting with REST APIs (or even when browsing the internet), you might see part of the URL changing, while the other part stays constant. The constant part is the endpoint that you are interacting with, while the changing part is the path variable.
From the screenshot above, you see that the right side of the URL changes, depending on the channel that you are trying to access. This is an example of a path variable.
Path variables are usually a unique identifier which is added to the URL to specify which resource or document you are trying to access. They can be integers or strings, and they can appear at any part of the URL which comes after the host name.
It is also important to keep in mind that there could be more than one path variable inside an URL such as when accessing sub documents or children entities. Take for example an online support ticketing system. A ticket can have one or more comments. If you plan to edit a comment related to a ticket, then your URI could look something like this:
hostname:1234/tickets/23/comments/1
In this example, you indicate that you would like to access the 1st comment of the ticket with ID 23. The 1 here can also represent another internal identifier. It all depends on the design of the system.
How to configure path variables in a Spring controller
Configuring path variables in a Spring controller can be done using the @PathVariable annotation. The @PathVariable annotation is added to the controller’s method arguments to indicate that their values are provided by the caller via path variables.
The location of the path variables in the URI is configured in the mapping annotation of the method (@RequestMapping, @GetMapping, @PatchMapping, etc..). Let us check the following example.
@RestController
@RequestMapping(path = "/api/tickets")
public class TicketController {
.........................
@GetMapping("/{id}")
public ResponseEntity getTicketById(@PathVariable long id){
Ticket ticket = ticketRepository.findById(id)
.orElseThrow(() -> new ItemNotFoundException("Ticket with ID:["+id+"] was not found"));
TicketRestModel ticketRestModel = new TicketRestModel();
ticketRestModel.setIdentifier(ticket.getId());
ticketRestModel.setTitle(ticket.getTitle());
ticketRestModel.setDescription(ticket.getDescription());
ticketRestModel.setStatus(ticket.getStatus());
return ResponseEntity.ok(ticketRestModel);
}
Let us go through the code snippet above:
- The @RequestMapping on the controller class is used to configure the path for the endpoint that the controller represents.
- The @GetMapping on the method is configured with the value “/{id}” to indicate that a path variable is expected when the GET method is called. The path variable is designated with the name id by adding it between the curly brackets.
- The method argument id is annotated with the annotation @PathVariable to indicate that the value of the argument will be provided by the caller as a path variable.
Since the variable has the name “id”, we do not need to set the variable name inside the mapping annotation. However, the variable can have a different name by setting the name inside the @PathVariable annotation.
@GetMapping("/{id}")
public ResponseEntity getTicketById(@PathVariable(name = "id") long ticketId){
Please note path variables are mandatory by default. If the user does not supply the path variable then Spring will consider that the user is trying to call a different function.
Optional path variables
As mentioned previously, path variables are mandatory by default. However, since Spring 4.3.3, you can configure path variables to be optional.
In order to configure an “optional” path variable, you will need to do a few things. First, you will need to set the “required” property of the @PathVariable annotation to false. Second, you will need to set up an alternative path for your mapped method which does not include the path variable.
Finally, you will need to use a non-primitive datatype as your method argument. This could be an object such as String or a numerical wrapper. You can also use an Optional as your argument type. This is needed as you cannot assign a null value to a primitive.
Let us take a look at the following example:
@GetMapping({"/{id}", ""})
public ResponseEntity getTicketById(@PathVariable(name = "id", required = false) Optional<Long> ticketId){
if(ticketId.isPresent())
......
In this example, we configured the @GetMapping annotation with two paths. One which expects the id to be provided by the user. The second is an empty string, which indicates that we do not expect any path variables. In this example, we used an optional which we could check for the presence of the value inside our controller method.
Configuring multiple path variables
There are cases where multiple path variables are needed. For example, if you are running your own social media website. A post has an ID, and a comment on that post has its own ID.
It can be that the primary key of that comment is a composite ID of both the post’s primary key and the comment’s number. In such case, you will need to indicate to the system which comment are you trying to access. Here, we will need more than one path variable.
Luckily, it is quite easy in Spring to configure multiple path variables. All you need to do is to adapt the mapping path and to annotate all method arguments whose values are expected from the user.
Let us check the following example:
@GetMapping({"/{ticketId}/comment/{commentNumber}", ""})
public ResponseEntity getTicketComment(@PathVariable(name = "ticketId") Long ticketId,
@PathVariable(name = "commentNumber") Long commentNum){
In this example, we configured two path variables. One is the “ticketId” and the other “commentNumber”. Naturally, we also configured two method arguments, both having the @PathVariable annotation.
Notice that we added the string “comment” between the two path variables in the path. This is recommended to make your URIs more readable and understandable. However, it is also optional.
When we try to call this endpoint, it will look as follows:
<myhost>/api/tickets/12/comment/1
Please keep in mind that the order of the path variables is important when calling the endpoint. Inside our controller method, the order of the method arguments is not that important as the variables are mapped by their name and not by the order in which they appear.
Using Map as a path variable
Another way to configure multiple path variables is to use a Map as the data type for your method arguments. So far (as of Spring 5.2.2), only Map<String, String> is officially supported. If such a map is found among the method arguments with the @PathVariable annotation, then Spring will simply populate the map with all of the path variables that exist in the path.
@GetMapping({"/{ticketId}/comment/{commentNumber}"})
public ResponseEntity getTicketComment(@PathVariable Map<String, String> pathVars){
Long ticketId = Long.parseLong(pathVars.get("ticketId"));
You can then access the required path variables by the name they were declared within the path mapping. This can be a useful feature if your controller method maps to multiple paths due to optional path variables as you can simply access the entries in the map that you need.
Here is another way to configure the previous example without using the @GetMapping annotation.
@RequestMapping(path = "/{ticketId}/comment/{commentNumber}", method = RequestMethod.GET)
public ResponseEntity getTicketComment(@PathVariable Map<String, String> pathVars){
Long ticketId = Long.parseLong(pathVars.get("ticketId"));
Summary and further info
In this post, we discussed how to configure Spring controllers to use path variables. As we saw, the process seems to be simple. However, the difficult part is to have a clean design which is easy to use and understand by the end user.
Please keep in mind that the instructions mentioned in this tutorial are valid for both web controllers and REST controllers. Also keep in mind that you can use the techniques discussed in this post in a Spring based and a Spring Boot application.
If you liked this post, then please share it with your friends, coworkers and your family, and if you have any questions, then let us know in the comments below.