Spring MVC - #RequestParam causing MissingServletRequestParameterException with x-www-form-urlencoded - spring-mvc

The follwing Spring MVC code throws MissingServletRequestParameterException,
#Controller
#RequestMapping("/users")
public class XXXXResource extends AbstractResource {
.....
#RequestMapping(method = RequestMethod.PUT
, produces = {"application/json", "application/xml"}
, consumes = {"application/x-www-form-urlencoded"}
)
public
#ResponseBody
Representation createXXXX(#NotNull #RequestParam("paramA") String paramA,
#NotNull #RequestParam("paramB") String paramB,
#NotNull #RequestParam("paramC") String paramC,
#NotNull #RequestParam("paramD") String paramD ) throws Exception {
...
}
}
There are no stack traces in the logs, only the Request from Postman returns with HTTP 400 Error.

if you want to Content-type:application/x-www-form-urlencoded means that the body of the HTTP request sent to the server should be one giant string -- name/value pairs are separated by the ampersand (&) and will be urlencoded as his name implies.
name=name1&value=value2
this means that you should not user #RequestParam because the arguments are passed in the body of the http request.
So if you want to use this content-type from their doc:
You convert the request body to the method argument by using an
HttpMessageConverter. HttpMessageConverter is responsible for
converting from the HTTP request message to an object and converting
from an object to the HTTP response body. The
RequestMappingHandlerAdapter supports the #RequestBody annotation with
the following default HttpMessageConverters:
ByteArrayHttpMessageConverter converts byte arrays.
StringHttpMessageConverter converts strings.
FormHttpMessageConverter converts form data to/from a MultiValueMap.
SourceHttpMessageConverter converts to/from a
javax.xml.transform.Source.
You should use #RequestBody with FormHttpMessageConverter, which will get this giant string and will convert it to MultiValueMap<String,String>. Here is a sample.
#RequestMapping(method = RequestMethod.PUT
, consumes = {"application/x-www-form-urlencoded"}
,value = "/choice"
)
public
#ResponseBody
String createXXXX(#RequestBody MultiValueMap params) throws Exception {
System.out.println("params are " + params);
return "hello";
}

Related

Spring MockMVC for Post Request and Bean Validation

i have a common problem to test Post-Request with Bean Validation.
Requirements: ContentType is APPLICATION_FORM_URLENCODED and NOT JSON
Model under test:
public class Message extends Auditable{
#Id
private long id;
private String messageText;
#NotNull
private Link link;
}
How it works on browser properly:
I'am just submit data. On Browser Dev-Tools, i see, that browser sends
only to fields: messageText="my message" and link="1"
problem: during MockMVC Post-Request, i can not convert param-value "1" to the object Link.
this.mockMvc.perform(MockMvcRequestBuilders.post("/links/link/comments")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("link", "1")
.param("messageText", "hello message"))
.andDo(print())
.andExpect(status().is3xxRedirection());
}
My post-handler on controller
#PostMapping(value = "/link/message")
public String saveNewComment(#Valid Message message, BindingResult bindingResult, RedirectAttributes attributes) {
if(bindingResult.hasErrors()) {
model.addAttribute("newMessage", message);
return "link/submit";
}
}
BindingResult complains about TypeMismatch from "String" to "Link".
How can i pass some Marschal- or Converter-Object, which enables BindingResult to convert string-value to appropriate object?
I don't want to implement on server-side own validator-object (which implements validator interface), cause on production it works properly without any additional code.
I've solved this issue by myself with small fix. Nested objects should parameterize with explizit field-name. Instead of param("link", "1") it must be param("link.id", "1")

Spring controller return string as pure json

I am returning a string object from spring controller like
#RequestMapping(value = "/persons.html", method = RequestMethod.GET)
public #ResponseBody String listPersonHtml(Model model) {
return "{\"abc\":\"test\"}";
}
I am getting response on ui like "{\"abc\":\"test\"}",i want this response as
{"abc":"test"}
i.e pure json object.
what type of configuration I need?
On UI side,if I set Accept */* then I face this issue,if I set Accept text/html or Accept text/plain then no issue is there,but I can't change accept header.
I found the way.Its all about spring message-converters.I added MappingJackson2HttpMessageConverter in this list and this converter tries to convert string to json and produces this result.
Just add org.springframework.http.converter.StringHttpMessageConverter before MappingJackson2HttpMessageConverter so that StringHttpMessageConverter can come into action and string can be returned as it is.
Old question, but I just had to solve the same issue and most of the answers I found resulted misleading, so here's mine:
It all starts with the Controller, and Spring trying to answer a mapped request in the format that the invoking client is expecting. The client can inform this using different HTTP features, and there is where the different HttpMessageConverter implementations are involved. Spring pick's the format to answer based on different strategies, applied by the ContentNegotiationManager.
By prioritizing StringHttpMessageConverter over MappingJackson2XmlHttpMessageConverter you are only telling Spring to answer in "text/plain" format over "application/json", and it will work until a client specifies that is expecting a json response (this is mostly done by setting the Accept header in the request, although there are other ways to do it). The important thing is that if a client sets that header to "application/json", Spring will use MappingJackson2XmlHttpMessageConverter that will translate the Java String to a Json String, ending up with something like "{\"abc\":\"test\"}" instead of {"abc":"test"}
So, the real issue that every developer faces in this case is that MappingJackson2XmlHttpMessageConverter translates a Java String to a Json String, and in some cases, you might not need that, because the string contains valid json that needs to be returned without modifications. There are some configuration classes for this MessageConverter but I did'n went that road, because I need to return Strings like "raw" Json only in some specific endpoints (performance is the key driver). Here's an expample that resumes my "approach":
#RestController
#RequestMapping(value = "test", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class TestController {
#RequestMapping(method = RequestMethod.GET, value = "endpoint")
public JsonObject getSomeJson() {
return new JsonObject("{\"abc\":\"test\"}");
}
private static class JsonObject {
private String rawJsonValue;
JsonObject(String rawJsonValue) {
this.rawJsonValue = rawJsonValue;
}
#JsonValue #JsonRawValue
public String getRawJsonValue() {
return rawJsonValue;
}
}
}
#JsonValue and #JsonRawValue are Jackson annotations that tell MappingJackson2XmlHttpMessageConverter to treat the getRawJsonValue method result as the Json representation of JsonObject, without making any modification. The response of the endpoint will be {"abc":"test"}

Spring MVC: Deserialise query parameters to POJO using Jackson objectMapper

Spring web application configuration contains Jackson ObjectMapper configured like this
objectMapper.disable(ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
objectMapper.registerModule(new JavaTimeModule())
JavaTimeModule is added to handle deserialisation of ZonedDateTime. There are two endpoint which handle a POJO which contains ZonedDateTime. The POJO is like this:
class MyRequest {
ZonedDateTime from
ZonedDateTime to
}
and controller with endpoints is:
#Slf4j
#RestController
class MyController {
#GetMapping('/pojo')
void getPojo(MyRequest myRequest) {
log.debug("Request received: $myRequest")
}
#PostMapping('/pojo')
void postPojo(#RequestBody MyRequest myRequest) {
log.debug("Request received: $myRequest")
}
}
When I send POST /pojo with body
{"from": "2017-03-15T00:00:00Z", "to": "2017-03-16T00:00:00Z"}
The response is 200 and deserialisation is successful.
Contrary, when I send
GET /pojo?from=2017-03-15T00:00:00Z&to=2017-03-15T00:00:00Z
The 400 Bad Request is received with error
Failed to convert from type [java.lang.String] to type [java.time.ZonedDateTime] for value '2017-03-15T00:00:00Z'
This make sense, since in GET request, I'm not sending JSON and therefore JSON object mapper is not called.
Is there a way to use objectMapper for GET requests also, so query parameters are converted into POJO object?
By the way, I know that it can be deserialised for GET endpoint like below, but I want to use same converter for GET and POST endpoint
#DateTimeFormat(iso = ISO.DATE_TIME)
ZonedDateTime from
#DateTimeFormat(iso = ISO.DATE_TIME)
ZonedDateTime to
Injecting objectMapper and converting query parameters map into object solves the problem
#Slf4j
#RestController
class MyController {
#Autowired
private ObjectMapper objectMapper
#GetMapping('/pojo')
void getPojo(#RequestParam Map<String, String> allRequestParams) {
MyRequest request = objectMapper.convertValue(allRequestParams, MyRequest)
log.debug("Request received: $myRequest")
}
...

Forward request to spring controller

From a servlet , I am forwarding the request to a spring controller like below
RequestDispatcher rd = request.getRequestDispatcher("/myController/test?reqParam=value");
rd.forward(request, response);
In order to pass a parameter to the spring controller , I am passing it as request parameter in the forward URL.
Is there a better way of doing this ?
Instead of passing in request parameter , can I pass as a method parameter to the spring controller during forward ?
This will be called when /myController/test?reqParam=value is requested:
#RequestMapping("/myController/test")
public String myMethod(#RequestParam("reqParam") String reqParamValue) {
return "forward:/someOtherUrl?reqParam="+reqParamValue; //forward request to another controller with the param and value
}
Or you can alternatively do:
#RequestMapping("/myController/test")
public String myMethod(#RequestParam("reqParam") String reqParamValue,
HttpServletRequest request) {
request.setAttribute("reqParam", reqParamValue);
return "forward:/someOtherUrl";
}
you can use path variable such as follow without need to use query string parameter:
#RequestMapping(value="/mapping/parameter/{reqParam}", method=RequestMethod.GET)
public #ResponseBody String byParameter(#PathVariable String reqParam) {
//Perform logic with reqParam
}

Jackson JSON mapping of Strings

I used POJO's as #RequestBody parameters and they worked fine. But now I try something like this public void func(#RequestBody String s) and cant pass the value to String s. When I used POJO's I writes {"attribute" : "value"} in request and the question is what I should write in POSTMAN to pass the value?
If you want to send only string value then it is better idea to use #RequestParam and in postman client you can send key value parameter.
public String method(#RequestParam("key") String value){
}

Resources