38

Is there a way to express that my Spring Web MVC controller method should be matched either by a request handing in a ID as part of the URI path ...

@RequestMapping(method=RequestMethod.GET, value="campaigns/{id}")
public String getCampaignDetails(Model model, @PathVariable("id") Long id) {

... or if the client sends in the ID as a HTTP request parameter in the style ...

@RequestMapping(method=RequestMethod.GET, value="campaigns")
public String getCampaignDetails(Model model, @RequestParam("id") Long id) {

This seems to me a quite common real-world URL scheme where I don't want to add duplicate code, but I wasn't able to find an answer yet. Any advice highly welcome.

EDIT: It turns out that there seems currently (with Spring MVC <= 3.0) no way to achieve this, see discussion inside Javi's answer.

Daniel Perník
  • 5,464
  • 2
  • 38
  • 46
ngeek
  • 7,733
  • 11
  • 36
  • 42

3 Answers3

50

You can set both mapping url for the same function and setting id as optional.

@RequestMapping(method=RequestMethod.GET, value={"/campaigns","/campaigns/{id}"})
public String getCampaignDetails(Model model,
     @RequestParam(value="id", required=false) Long id,
     @PathVariable("id") Long id2)
{
}

though it would map as well when id is not sent, but you can control this inside the method.

EDIT: The previous solution doesn't work because @PathVariable is not set to null when there isn't {null} and it cannot map the URL (thanks ngeek). I think then that the only possible solution is to create two methods each one mapped with its @MappingRequest and inside one of them call the other function or redirect to the other URL (redirect: or forward: Spring prefixes). I know this solution is not what you're looking for but think it's the best you can do. Indeed you're not duplicating code but you're creating another function to handle another URL.

sharptooth
  • 167,383
  • 100
  • 513
  • 979
Javi
  • 19,387
  • 30
  • 102
  • 135
  • 1
    Unfortunately this is not working, since the path variable cannot be resolved. I also tried a variant by adding to the method parameters (@PathVariable("id") Long id2) but then you cannot make a path variable optional, therefore this proposed solution does not work. Any more recommendations? – ngeek May 01 '10 at 09:55
  • @ngeek I didn't realize that @PathVariable couldn't be set to null, thanks. I had also forget to write @PathVariable so I've edited my answer to add it and I think you cannot avoid to have 2 methods I think :(. – Javi May 01 '10 at 14:09
  • 1
    Thanks for coming back to your proposal Javi. Sure thing I was already delegating to the same internal method to avoid to duplicate code. I still think that Spring MVC annotations should provide a mean to map multiple URL defintions to the same method by intrinsic mean, like routers in Rails or Grails do already allow. Thanks for your help anyway. – ngeek May 02 '10 at 14:37
  • This example was a bit confusing to me with adding an id2. Anyone else confused by this might appreciate knowing that this is valid: @PathVariable @RequestParam(value = "id", required = false) Long id, – Software Prophets Nov 08 '14 at 18:28
  • 1
    @OutfastSource `@PathVariable` seems to be ignored when I do that. What worked for me was @Javi's answer with `required = false` in the `@PathVariable` which is supported since Spring MVC 4.3.3. – Helder Pereira Mar 08 '17 at 07:53
4

If you still want to stick to PathVariable approach and if you are getting 400 syntactically incorrect error then follow this approach-

 @RequestMapping(method=RequestMethod.GET, value={"campaigns/{id}","campaigns"})
                         public String getCampaignDetails(Model model,
                         @PathVariable Map<String, String> pathVariables) 
   {

     System.out.println(pathVariables.get("id"));

   }
vivex
  • 2,496
  • 1
  • 25
  • 30
2

The @RequestMapping annotation now supports setting the path attribute instead of name or value. With path, you can achieve the mapping desired by this question:

@RequestMapping(method=RequestMethod.GET, path="campaigns/{id}")
public String getCampaignDetails(Model model, @PathVariable("id") Long id) {

@RequestMapping(method=RequestMethod.GET, value="campaigns")
public String getCampaignDetails(Model model, @RequestParam("id") Long id) {
Jpnh
  • 806
  • 1
  • 11
  • 22