Friday, 8 January 2016

Making @PathVariable optional in Spring MVC

Background



A simple controller class would look like - 


    @RequestMapping(value="/home/{name}", method = RequestMethod.GET)
    public String welcome(Model model,  @PathVariable(value="name") String name, @RequestParam(value="surname") String surname) {
        model.addAttribute("test", "TestValue");
        System.out.println("Name : " + name);
        System.out.println("Surname : " + surname);
        return "welcome";
    }

This controller method basically expects a URL like 
  • http://localhost:8080/WebProject/home/aniket?surname=thakur
where aniket is the name (path variable) where as thakur is the surname (request param).

Here you cannot have name or surname blank. If you do you will get an error.  So URLs like -
  • http://localhost:8080/WebProject/home?surname=thakur OR

    You will get  HTTP Status 404 - Requested Resource is not available
  • http://localhost:8080/WebProject/home/aniket

    You will get - HTTP Status 400 - Required String parameter 'surname' is not present
 will not work!

 Only URL that will work as mentioned above is -

  • http://localhost:8080/WebProject/home/aniket?surname=thakur
For this URL you should see output as -

Name : aniket
Surname : thakur

However you can make @RequestParam option. Spring provides you this functionality. All you have to do is set it's required property as false i.e
  • @RequestParam(value="surname", required=false) String surname
Now you can hit the URL -
  •  http://localhost:8080/WebProject/home/aniket
and you should not see 404 error. Output would print -

Name : aniket
Surname : null

However there is no such parameter in @PathVariable. So you cannot truly make it optional. However there are alternative and we will look at them now.

 Making @PathVariable optional in Spring MVC

There are two way in which you can work - 

  1. Provide two paths in value - One with path param and one without. In Arguments take map of path params and check for null.


        @RequestMapping(value={"/home/{name}","/home"}, method = RequestMethod.GET)
        public String welcome(@PathVariable Map<String, String> pathVariablesMap) {
            if (pathVariablesMap.containsKey("name")) {
                //corresponds to path "/home/{name}"
                System.out.println("With Name : " + pathVariablesMap.get("name"));
            } else {
                //corresponds to path "/home"
                System.out.println("With No Name");
            }   
            return "welcome";
        }
    


    Here you are essentially saying this controller will map to both URLS - "/home/{name}" and "/home" and if you do get name in pathparams map then the URL was "/home/{name}" else it was "/home".

    And now if you hit http://localhost:8080/WebProject/home/aniket you should get output as - "With Name : aniket" and if you hit http://localhost:8080/WebProject/home you should see output - "With No Name".
  2.  Another way is to use java.util.Optional provided by Java8. So if you are using Spring 4.1 and Java 8 you can use java.util.Optional which is supported in @RequestParam, @PathVariable, @RequestHeader and @MatrixVariable in Spring MVC -

        @RequestMapping(value="/home/{name}", method = RequestMethod.GET)
        public String welcome(@PathVariable Optional<String> name) {
            if (name.get() != null) {
                //corresponds to path "/home/{name}"
                System.out.println("With Name : " + name.get());
            } else {
                //corresponds to path "/home"
                System.out.println("With No Name");
            }   
            return "welcome";
        }
    


    Repeat same test as point 1. You should get same result. This is just another alternate way.

So as we have seen pathvariables cannot truly be null but there are workarounds. This case should not typically arise as it is poor design. You should always have some value in path param. If you are certain it can be null better make it a @RequestPram and use requiref=false.


Related Links

1 comment:

  1. name.get() looks to throw an exception, maybe a better option is name.orElse(null) for the if statement

    ReplyDelete

t> UA-39527780-1 back to top