Using HTTP dev client with Post request and Content-Type application/x-www-form-urlencoded
1) Only @RequestBody
Request - localhost:8080/SpringMVC/welcome In Body - name=abc
Code-
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, Model model) {
model.addAttribute("message", body);
return "hello";
}
// Gives body as 'name=abc' as expected
2) Only @RequestParam
Request - localhost:8080/SpringMVC/welcome In Body - name=abc
Code-
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, Model model) {
model.addAttribute("name", name);
return "hello";
}
// Gives name as 'abc' as expected
3) Both together
Request - localhost:8080/SpringMVC/welcome In Body - name=abc
Code-
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, @RequestParam String name, Model model) {
model.addAttribute("name", name);
model.addAttribute("message", body);
return "hello";
}
// HTTP Error Code 400 - The request sent by the client was syntactically incorrect.
4) Above with params position changed
Request - localhost:8080/SpringMVC/welcome In Body - name=abc
Code-
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, @RequestBody String body, Model model) {
model.addAttribute("name", name);
model.addAttribute("message", body);
return "hello";
}
// No Error. Name is 'abc'. body is empty
5) Together but get type url parameters
Request - localhost:8080/SpringMVC/welcome?name=xyz In Body - name=abc
Code-
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, @RequestParam String name, Model model) {
model.addAttribute("name", name);
model.addAttribute("message", body);
return "hello";
}
// name is 'xyz' and body is 'name=abc'
6) Same as 5) but with parameters position changed
Code -
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, @RequestBody String body, Model model) {
model.addAttribute("name", name);
model.addAttribute("message", body);
return "hello";
}
// name = 'xyz,abc' body is empty
Can someone explain this behaviour?
You could also just change the @RequestParam default required status to false so that HTTP response status code 400 is not generated. This will allow you to place the Annotations in any order you feel like.
It happens because of not very straight forward Servlet specification. If you are working with a native
HttpServletRequest
implementation you cannot get both the URL encode body and the parameters. Spring does some workarounds, which make it even more strange and nontransparent.In such cases Spring (version 3.2.4) re-renders a body for you using data from the
getParameterMap()
method. It mixes GET and POST parameters and breaks the parameter order. The class, which is responsible for the chaos isServletServerHttpRequest
. Unfortunately it cannot be replaced, but the classStringHttpMessageConverter
can be.The clean solution is unfortunately not simple:
StringHttpMessageConverter
. Copy/Overwrite the original class adjusting methodreadInternal()
.HttpServletRequest
overwritinggetInputStream()
,getReader()
andgetParameter*()
methods.In the method StringHttpMessageConverter#readInternal following code must be used:
Then the converter must be registered in the context.
The step two is described here: Http Servlet request lose params from POST body after read it once
The
@RequestBody
javadoc statesIt uses registered instances of
HttpMessageConverter
to deserialize the request body into an object of the annotated parameter type.And the
@RequestParam
javadoc statesSpring binds the body of the request to the parameter annotated with
@RequestBody
.Spring binds request parameters from the request body (url-encoded parameters) to your method parameter. Spring will use the name of the parameter, ie.
name
, to map the parameter.Parameters are resolved in order. The
@RequestBody
is processed first. Spring will consume all theHttpServletRequest
InputStream
. When it then tries to resolve the@RequestParam
, which is by defaultrequired
, there is no request parameter in the query string or what remains of the request body, ie. nothing. So it fails with 400 because the request can't be correctly handled by the handler method.The handler for
@RequestParam
acts first, reading what it can of theHttpServletRequest
InputStream
to map the request parameter, ie. the whole query string/url-encoded parameters. It does so and gets the valueabc
mapped to the parametername
. When the handler for@RequestBody
runs, there's nothing left in the request body, so the argument used is the empty string.The handler for
@RequestBody
reads the body and binds it to the parameter. The handler for@RequestParam
can then get the request parameter from the URL query string.The handler for
@RequestParam
reads from both the body and the URL query String. It would usually put them in aMap
, but since the parameter is of typeString
, Spring will serialize theMap
as comma separated values. The handler for@RequestBody
then, again, has nothing left to read from the body.It's too late to answer this question, but it could help for new readers, It seems version issues. I ran all these tests with spring 4.1.4 and found that the order of
@RequestBody
and@RequestParam
doesn't matter.body= "name=abc"
, andname = "abc"
body ="name=abc"
,name = "xyz,abc"