42

I am doing a Spring web. For a controller method, I am able to use RequestParam to indicate whether a parameter it is required or not. For example:

@RequestMapping({"customer"}) 
public String surveys(HttpServletRequest request, 
@RequestParam(value="id", required = false) Long id,            
Map<String, Object> map)

I would like to use PathVariable such as the following:

@RequestMapping({"customer/{id}"}) 
public String surveys(HttpServletRequest request, 
@PathVariable("id") Long id,            
Map<String, Object> map) 

How can I indicate whether a path variable is required or not? I need to make it optional because when creating a new object, there is no associated ID available until it is saved.

Thanks for help!

tereško
  • 58,060
  • 25
  • 98
  • 150
curious1
  • 14,155
  • 37
  • 130
  • 231
  • 2
    I think you're asking for the same thing as this question http://stackoverflow.com/questions/4904092/with-spring-3-0-can-i-make-an-optional-path-variable – TravJenkins Jul 23 '13 at 22:03
  • 1
    Possible duplicate of [With Spring 3.0, can I make an optional path variable?](https://stackoverflow.com/questions/4904092/with-spring-3-0-can-i-make-an-optional-path-variable) – Oleg Estekhin Aug 03 '17 at 16:38

7 Answers7

63

VTTom`s solution is right, just change "value" variable to array and list all url possibilities: value={"/", "/{id}"}

@RequestMapping(method=GET, value={"/", "/{id}"})
public void get(@PathVariable Optional<Integer> id) {
  if (id.isPresent()) {
    id.get()   //returns the id
  }
}
Community
  • 1
  • 1
Martin Cmarko
  • 661
  • 5
  • 7
  • 3
    Note this requires Spring 4.1+ and Java 1.8. – Aniket Thakur Jan 08 '16 at 06:39
  • 1
    When you use `"/"` you will get 404 for `http://.../foo/bar` address and response for `http://.../foo/bar/`. I used empty string `""` as first value. – csharpfolk Nov 06 '16 at 18:11
  • 4
    Note that since Spring 4.3.3, you can also go with `@PathVariable(required = false)` and get null if the variable is not present. – Nicolai Ehemann Oct 05 '17 at 14:18
  • 4
    @NicolaiEhemann, Thank you for the information, which is correct. But that only doesn't work. It could be like `@RequestMapping(value = {"foo", "foo/{bar}"})` and `@PathVariable(required = false)` – Ram Nov 04 '17 at 13:56
  • My comment is only an addition to the answer (which should be the accepted answer) in that it provides an alternative to making the id Optional. – Nicolai Ehemann Mar 06 '18 at 10:47
42

There's no way to make it optional, but you can create two methods with one having the @RequestMapping({"customer"}) annotation and the other having @RequestMapping({"customer/{id}"}) and then act accordingly in each.

Tristan
  • 1,077
  • 11
  • 16
  • 1
    Tristan, I selected the VTTom's post as the answer to benefit whoever reads this question. Your was right. – curious1 Dec 18 '15 at 18:14
  • It is possible, see some of the other answers (@curious1 feel free to accept one of the other answers). – rogerdpack Apr 16 '19 at 17:08
  • it is possible to use that way: `@PatchMapping(path = {"/{procurementInitId}/updateLinkedPurchase/{purchaseId}", "/{procurementInitId}/updateLinkedPurchase"}, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public HttpEntity> updateLinkedPurchase( @PathVariable(name = "procurementInitId") Integer procurementInitId, @PathVariable(name = "purchaseId", required = false) Integer purchaseId) {` – Michael Gaev Jul 03 '20 at 14:36
35

I know this is an old question, but searching for "optional path variable" puts this answer high so i thought it would be worth pointing out that since Spring 4.1 using Java 1.8 this is possible using the java.util.Optional class.

an example would be (note the value must list all the potential routes that needs to match, ie. with the id path variable and without. Props to @martin-cmarko for pointing that out)

@RequestMapping(method=GET, value={"/", "/{id}"})
public void get(@PathVariable Optional<Integer> id) {
  if (id.isPresent()) {
    id.get()   //returns the id
  }
}
VTTom
  • 503
  • 4
  • 9
3

VTToms answer will not work as without id in path it will not be matched (i.e will not find corresponding HandlerMapping) and consequently controller will not be hit. Rather you can do -

@RequestMapping({"customer/{id}","customer"}) 
public String surveys(HttpServletRequest request, @PathVariable Map<String, String> pathVariablesMap, Map<String, Object> map) {
    if (pathVariablesMap.containsKey("id")) {
        //corresponds to path "customer/{id}"
    }
    else {
        //corresponds to path "customer"
    }
}

You can also use java.util.Optional which others have mentioned but it requires requires Spring 4.1+ and Java 1.8..

Aniket Thakur
  • 66,731
  • 38
  • 279
  • 289
1

I know this is an old question, but as none of the answers provide some updated information and as I was passing by this, I would like to add my contribution:

Since Spring MVC 4.3.3 introduced Web Improvements,

@PathVariable(required = false) //true is default value

is legal and possible.

Giorgi Tsiklauri
  • 9,715
  • 8
  • 45
  • 66
0

There is a problem with using 'Optional'(@PathVariable Optional id) or Map (@PathVariable Map pathVariables) in that if you then try to create a HATEOAS link by calling the controller method it will fail because Spring-hateoas seems to be pre java8 and has no support for 'Optional'. It also fails to call any method with @PathVariable Map annotation.

Here is an example that demonstrates the failure of Map

   @RequestMapping(value={"/subs","/masterclient/{masterclient}/subs"}, method = RequestMethod.GET)
   public List<Jobs>  getJobListTest(
         @PathVariable  Map<String, String> pathVariables,
         @RequestParam(value="count", required = false, defaultValue = defaultCount) int count) 
   {

      if (pathVariables.containsKey("masterclient")) 
      {
         System.out.println("Master Client = " + pathVariables.get("masterclient"));
      } 
      else 
      {
         System.out.println("No Master Client");
      }




  //Add a Link to the self here.
  List list = new ArrayList<Jobs>();
  list.add(linkTo(methodOn(ControllerJobs.class).getJobListTest(pathVariables, count)).withSelfRel());

  return list;

}

Ashutosh
  • 15
  • 7
0
@RequestMapping(path = {"/customer", "/customer/{id}"})
public String getCustomerById(@PathVariable("id") Optional<Long> id) 
        throws RecordNotFoundException 
{
    if(id.isPresent()) {
        //get specific customer
    } else {
        //get all customer or any thing you want
    }
}

Now all URLs are mapped and will work.

/customer/123

/customer/1000

/customer - WORKS NOW !!