Spring 4.3 allows query params to override path variables from @RequestMapping

Viewed 874

Apparenly, Spring (4.3) accepts query parameters preferentially over path variables, allowing them to override the path variables when using @RequestMapping with path variables, and binding the request values to a model class. Given a controller like below:

@Controller
@RequestMapping("/")
public class HelloWorldController {
    @RequestMapping(value = "/hi/{name}", method = RequestMethod.GET)
    public @ResponseBody Greeting sayHello(
            @Valid HelloParams helloParams) {
        return new Greeting(helloParams.toString());
    }

If we request a URL such as /hi/good?name=bad, we will see that the name param takes the value "bad". Thus, the "good" value which was present in the URL path in the {name} position gets ignored or overridden by the query parameter. If we remove the "name" query parameter, we get the "good" value instead.

Note: I am using a model class so that I can do validation on multiple fields of the model. I can't fix this just by using @PathVariable on a simple method parameter.

Why is this? I don't see any mention of this behavior in the documentation.

Is there any workaround to prevent it from happening?

This behavior by Spring also appears to be confusing Springfox Swagger: in this case the Swagger UI generates URLs such as: /hi/{name}?name=foo (with literal curly brackets present in the URL). And, oddly enough, it works due to the behavior described above. However, it's misleading to people who use the API because they should use path params!

2 Answers

It is exactly same as my problem. My Solution is @ApiImplicitParams. My Spring Boot Sample(with Lombok) code is below.

@ApiOperation(value = "Test API.")
@RequestMapping(value = "/test/{memberId}", method = RequestMethod.GET)
@ApiImplicitParams({
    @ApiImplicitParam(name = "memberId", value = "member identification number", paramType = "path", dataType = "long", required = true)
})
public ResponseEntity<String> testMethod(@Valid CommandObject command) {
    return "hello, world~"
}

@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class CommandObject {
    private long memberId
}

@ApiImplicitParam's name is the field name of CommandObject. Important part is paramType. It's value is whether path or query. path for path variables and query for query parameters.

Also, refer to documentation: http://docs.swagger.io/swagger-core/v1.5.X/apidocs/index.html?io/swagger/annotations/ApiModelProperty.html

If you want to use same variable for both requestparam and pathvariable, then you can create method like below:

@RequestMapping(value = "/hi/{name}", method = RequestMethod.GET)
    public @ResponseBody String sayHello(@PathVariable(value="name") String name1, @RequestParam(value="name") String name2)
Related