35

Is it possible to make the @PathVariable to return null if the path variable is not in the url? Otherwise I need to make two handlers. One for /simple and another for /simple/{game}, but both do the same just if there is no game defined i pick first one from a list however if there is a game param defined then i use it.

@RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(@PathVariable("example") String example,
            HttpServletRequest request) {

And this is what I get when trying to open page /simple:

Caused by: java.lang.IllegalStateException: Could not find @PathVariable [example] in @RequestMapping

skaffman
  • 398,947
  • 96
  • 818
  • 769
Rihards
  • 10,241
  • 14
  • 58
  • 78

6 Answers6

42

They cannot be optional, no. If you need that, you need two methods to handle them.

This reflects the nature of path variables - it doesn't really make sense for them to be null. REST-style URLs always need the full URL path. If you have an optional component, consider making it a request parameter instead (i.e. using @RequestParam). This is much better suited to optional arguments.

skaffman
  • 398,947
  • 96
  • 818
  • 769
  • 4
    However, sine v4.3.3 Spring supports `@PathVariable(value = "siteId", required = false) String siteId` although I have not found a way *not to provide a path variable* or leave it blank or empty. – Alex Sep 15 '17 at 14:08
  • 2
    @Alex Although it's possible to set the `required` parameter to `false`, it will be still required to provide the path parameter, or, exception will be thrown. – securecurve Oct 29 '18 at 06:29
41

As others have already mentioned No you cannot expect them to be null when you have explicitly mentioned the path parameters. However you can do something like below as a workaround -

@RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(@PathVariable Map<String, String> pathVariablesMap,
            HttpServletRequest request) {
    if (pathVariablesMap.containsKey("game")) {
        //corresponds to path "/simple/{game}"
    } else {
        //corresponds to path "/simple"
    }           
}

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 = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(@PathVariable Optional<String> game,
            HttpServletRequest request) {
    if (game.isPresent()) {
        //game.get()
        //corresponds to path "/simple/{game}"
    } else {
        //corresponds to path "/simple"
    }           
}
Aniket Thakur
  • 66,731
  • 38
  • 279
  • 289
19

You could always just do this:

@RequestMapping(value = "/simple", method = RequestMethod.GET)
public ModelAndView gameHandler(HttpServletRequest request) {
    gameHandler2(null, request)
}

@RequestMapping(value = "/simple/{game}", method = RequestMethod.GET)
public ModelAndView gameHandler2(@PathVariable("game") String game,
        HttpServletRequest request) {
Shawn K.
  • 199
  • 1
  • 2
3
@RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(@PathVariable(value="example",required = false) final String example)

Try this approach, it worked for me.

ochs.tobi
  • 3,214
  • 7
  • 31
  • 52
1

I just tested this just now, but by combining the above solution i got this:

@RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(@PathVariable(value = "game", required = false) String example,
                                HttpServletRequest request) {
    if (example != null) {
        //...
    } else {
        //pick first, ...
    }
}

Now when you use "/simple", String example will be null instead of throwing Exception.

Short solution, no fancy Optional<> or Map<>

0

We can write multiple methods in controllers with explicit mapping with the path variable combination to exclude the optional variables (if using old version of Spring)

In my scenario wanted to develop an API to get recycle value for old device where parameters could be brand, model and network however network is an option one.

One option to handle this was use network as a request parameter instead of pathVariable. for e.g. /value/LG/g3?network=vodafone however I didn't like this approach.

for me the more cleaner one was to use below /refurbValue/LG/g3 /refurbValue/LG/g3/vodafone

   @RequestMapping(value = "/refurbValue/{make}/{model}/{network}", method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
def getRefurbValueByMakeAndModelAndNetwork(@PathVariable String make, @PathVariable String model, @PathVariable String network ) throws Exception {
    //logic here
}

@RequestMapping(value = "/refurbValue/{make}/{model}", method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
def getRefurbValueByMakeAndModel(@PathVariable String make, @PathVariable String model) throws Exception {
    //logic here
}

In the above example, both controller can use the same service method and handling of the parameter can be done. In my case I was using Groovy so it was easy to use with optional parameter like

Map getRefurbValue(String brand, String model, String network="")

Sanjay Bharwani
  • 3,317
  • 34
  • 31