With Spring can I make an optional path variable?
Asked Answered
I

10

213

With Spring 3.0, can I have an optional path variable?

For example

@RequestMapping(value = "/json/{type}", method = RequestMethod.GET)
public @ResponseBody TestBean testAjax(
        HttpServletRequest req,
        @PathVariable String type,
        @RequestParam("track") String track) {
    return new TestBean();
}

Here I would like /json/abc or /json to call the same method.
One obvious workaround declare type as a request parameter:

@RequestMapping(value = "/json", method = RequestMethod.GET)
public @ResponseBody TestBean testAjax(
        HttpServletRequest req,
        @RequestParam(value = "type", required = false) String type,
        @RequestParam("track") String track) {
    return new TestBean();
}

and then /json?type=abc&track=aa or /json?track=rr will work

Invidious answered 4/2, 2011 at 23:58 Comment(0)
M
215

You can't have optional path variables, but you can have two controller methods which call the same service code:

@RequestMapping(value = "/json/{type}", method = RequestMethod.GET)
public @ResponseBody TestBean typedTestBean(
        HttpServletRequest req,
        @PathVariable String type,
        @RequestParam("track") String track) {
    return getTestBean(type);
}

@RequestMapping(value = "/json", method = RequestMethod.GET)
public @ResponseBody TestBean testBean(
        HttpServletRequest req,
        @RequestParam("track") String track) {
    return getTestBean();
}
Mudd answered 5/2, 2011 at 0:6 Comment(5)
@Shamik: This is a compelling reason not to use path variables, in my opinion. The combinatorial proliferation can quickly get out of hand.Curia
Actually not because the path can't be that complex while being filled up with optional components. If you have more than one or max two optional path elements you should seriously consider switching a few of them to request parameters.Czardom
And for some people, having the second controller method call the first controller method may work as well, if for instance the differing parameter can be provided by some other meansPerni
Please consider updating your answer, instead of creating two controller methods in newer version of Spring we may just use @RequestMapping with two values as in: #17822231Bluebeard
omg how do you expect to maintain these endpoints ? And if instead of only one path variable we have 5, do the math for me, how many endpoints would you do ? Please do me a favor and replace @PathVariable to @RequestParamHermy
B
139

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 = {"/json/{type}", "/json" }, method = RequestMethod.GET)
public @ResponseBody TestBean typedTestBean(
    @PathVariable Optional<String> type,
    @RequestParam("track") String track) {      
    if (type.isPresent()) {
        //type.get() will return type value
        //corresponds to path "/json/{type}"
    } else {
        //corresponds to path "/json"
    }       
}
Bulgarian answered 8/1, 2016 at 6:52 Comment(5)
you sure this would work? Your own answer here suggests that the controller won't be hit if {type} is absent from the path. I think PathVariable Map would be a better approach, or using separate controllers.On
Yes if you just have "/json/{type}" and type is not present it will not be hit (as my linked answer suggests) but here you have value = {"/json/{type}", "/json" }. So if any one matches controller method will be hit.Bulgarian
Is it possible that the same value, etc., type would be RequestParam and RequestParam too?Perrins
It works, but anyway the controller's function won't be called because it expects a parameter thereHouseroom
This works, and since Spring 4.3.3, you can also go with @PathVariable(required = false) and get null if the variable is not present.Teets
H
83

It's not well known that you can also inject a Map of the path variables using the @PathVariable annotation. I'm not sure if this feature is available in Spring 3.0 or if it was added later, but here is another way to solve the example:

@RequestMapping(value={ "/json/{type}", "/json" }, method=RequestMethod.GET)
public @ResponseBody TestBean typedTestBean(
    @PathVariable Map<String, String> pathVariables,
    @RequestParam("track") String track) {

    if (pathVariables.containsKey("type")) {
        return new TestBean(pathVariables.get("type"));
    } else {
        return new TestBean();
    }
}
Hegelianism answered 13/3, 2015 at 17:8 Comment(1)
I use it all the time .This comes handy when i want a single method to handle different uri types ex:{ "/json/{type}","/json/{type}/{xyz}","/json/{type}/{abc}", "/json/{type}/{abc}/{something}","/json" }Rad
W
33

You could use a :

@RequestParam(value="somvalue",required=false)

for optional params rather than a pathVariable

Willette answered 20/8, 2012 at 20:40 Comment(5)
This is version specific, it seems. No go for Spring 3.Itinerary
Currently using this method for a spring 3.1 project, and the docs say that it works for 2.5+, so it definitely works for Spring 3. EDIT: source.Zigrang
True, but this is not what the question is about. Using request parameters is indeed mentioned in the question as "One obvious workaround", but the question itself is about path parameters. This is not a solution for optional path parameters.Number
PathVariable and RequestParam are different.Vaudois
This is explained well in this baeldung post linkBiceps
S
22

Spring 5 / Spring Boot 2 examples:

blocking

@GetMapping({"/dto-blocking/{type}", "/dto-blocking"})
public ResponseEntity<Dto> getDtoBlocking(
        @PathVariable(name = "type", required = false) String type) {
    if (StringUtils.isEmpty(type)) {
        type = "default";
    }
    return ResponseEntity.ok().body(dtoBlockingRepo.findByType(type));
}

reactive

@GetMapping({"/dto-reactive/{type}", "/dto-reactive"})
public Mono<ResponseEntity<Dto>> getDtoReactive(
        @PathVariable(name = "type", required = false) String type) {
    if (StringUtils.isEmpty(type)) {
        type = "default";
    }
    return dtoReactiveRepo.findByType(type).map(dto -> ResponseEntity.ok().body(dto));
}
Srinagar answered 11/4, 2018 at 9:19 Comment(0)
D
8

Simplified example of Nicolai Ehmann's comment and wildloop's answer (works with Spring 4.3.3+), basically you can use required = false now:

  @RequestMapping(value = {"/json/{type}", "/json" }, method = RequestMethod.GET)
  public @ResponseBody TestBean testAjax(@PathVariable(required = false) String type) {
    if (type != null) {
      // ...
    }
    return new TestBean();
  }
Deyoung answered 16/4, 2019 at 17:42 Comment(2)
@PathVariable(required = false) not supported when i tried?!Coaxial
What springboot version? Maybe it has been removed LOLDeyoung
S
3

Check this Spring 3 WebMVC - Optional Path Variables. It shows an article of making an extension to AntPathMatcher to enable optional path variables and might be of help. All credits to Sebastian Herold for posting the article.

Socher answered 10/10, 2014 at 17:42 Comment(0)
T
2

Here is the answer straight from baeldung's reference page :- https://www.baeldung.com/spring-optional-path-variables

Torras answered 7/7, 2020 at 14:59 Comment(0)
P
1

thanks Paul Wardrip in my case I use required.

@RequestMapping(value={ "/calificacion-usuario/{idUsuario}/{annio}/{mes}", "/calificacion-usuario/{idUsuario}" }, method=RequestMethod.GET)
public List<Calificacion> getCalificacionByUsuario(@PathVariable String idUsuario
        , @PathVariable(required = false) Integer annio
        , @PathVariable(required = false) Integer mes) throws Exception {
    return repositoryCalificacion.findCalificacionByName(idUsuario, annio, mes);
}
Pandiculation answered 19/1, 2021 at 18:20 Comment(0)
A
-5
$.ajax({
            type : 'GET',
            url : '${pageContext.request.contextPath}/order/lastOrder',
            data : {partyId : partyId, orderId :orderId},
            success : function(data, textStatus, jqXHR) });

@RequestMapping(value = "/lastOrder", method=RequestMethod.GET)
public @ResponseBody OrderBean lastOrderDetail(@RequestParam(value="partyId") Long partyId,@RequestParam(value="orderId",required=false) Long orderId,Model m ) {}
Amandaamandi answered 13/10, 2015 at 6:36 Comment(1)
You might want to edit in some text in your answer, explaining why you think this contributes to solving the issue at hand (4 years later).Decagram

© 2022 - 2024 — McMap. All rights reserved.