Is it necessary to wrap in a backing object? I want to do this:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody String str1, #RequestBody String str2) {}
And use a JSON like this:
{
"str1": "test one",
"str2": "two test"
}
But instead I have to use:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody Holder holder) {}
And then use this JSON:
{
"holder": {
"str1": "test one",
"str2": "two test"
}
}
Is that correct? My other option would be to change the RequestMethod to GET and use #RequestParam in query string or use #PathVariable with either RequestMethod.
While it's true that #RequestBody must map to a single object, that object can be a Map, so this gets you a good way to what you are attempting to achieve (no need to write a one off backing object):
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody Map<String, String> json) {
//json.get("str1") == "test one"
}
You can also bind to Jackson's ObjectNode if you want a full JSON tree:
public boolean getTest(#RequestBody ObjectNode json) {
//json.get("str1").asText() == "test one"
You are correct, #RequestBody annotated parameter is expected to hold the entire body of the request and bind to one object, so you essentially will have to go with your options.
If you absolutely want your approach, there is a custom implementation that you can do though:
Say this is your json:
{
"str1": "test one",
"str2": "two test"
}
and you want to bind it to the two params here:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
public boolean getTest(String str1, String str2)
First define a custom annotation, say #JsonArg, with the JSON path like path to the information that you want:
public boolean getTest(#JsonArg("/str1") String str1, #JsonArg("/str2") String str2)
Now write a Custom HandlerMethodArgumentResolver which uses the JsonPath defined above to resolve the actual argument:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.jayway.jsonpath.JsonPath;
public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArg.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String body = getRequestBody(webRequest);
String val = JsonPath.read(body, parameter.getMethodAnnotation(JsonArg.class).value());
return val;
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) servletRequest.getAttribute(JSONBODYATTRIBUTE);
if (jsonBody==null){
try {
String body = IOUtils.toString(servletRequest.getInputStream());
servletRequest.setAttribute(JSONBODYATTRIBUTE, body);
return body;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return "";
}
}
Now just register this with Spring MVC. A bit involved, but this should work cleanly.
For passing multiple object, params, variable and so on. You can do it dynamically using ObjectNode from jackson library as your param. You can do it like this way:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody ObjectNode objectNode) {
// And then you can call parameters from objectNode
String strOne = objectNode.get("str1").asText();
String strTwo = objectNode.get("str2").asText();
// When you using ObjectNode, you can pas other data such as:
// instance object, array list, nested object, etc.
}
I hope this help.
You can mix up the post argument by using body and path variable for simpler data types:
#RequestMapping(value = "new-trade/portfolio/{portfolioId}", method = RequestMethod.POST)
public ResponseEntity<List<String>> newTrade(#RequestBody Trade trade, #PathVariable long portfolioId) {
...
}
The easy solution is to create a payload class that has the str1 and the str2 as attributes:
#Getter
#Setter
public class ObjHolder{
String str1;
String str2;
}
And after you can pass
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody ObjHolder Str) {}
and the body of your request is:
{
"str1": "test one",
"str2": "two test"
}
#RequestParam is the HTTP GET or POST parameter sent by client, request mapping is a segment of URL which's variable:
http:/host/form_edit?param1=val1¶m2=val2
var1 & var2 are request params.
http:/host/form/{params}
{params} is a request mapping. you could call your service like : http:/host/form/user or http:/host/form/firm
where firm & user are used as Pathvariable.
Instead of using json, you can do simple thing.
$.post("${pageContext.servletContext.contextPath}/Test",
{
"str1": "test one",
"str2": "two test",
<other form data>
},
function(j)
{
<j is the string you will return from the controller function.>
});
Now in the controller you need to map the ajax request as below:
#RequestMapping(value="/Test", method=RequestMethod.POST)
#ResponseBody
public String calculateTestData(#RequestParam("str1") String str1, #RequestParam("str2") String str2, HttpServletRequest request, HttpServletResponse response){
<perform the task here and return the String result.>
return "xyz";
}
Hope this helps you.
I have adapted the solution of Biju:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
private ObjectMapper om = new ObjectMapper();
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArg.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String jsonBody = getRequestBody(webRequest);
JsonNode rootNode = om.readTree(jsonBody);
JsonNode node = rootNode.path(parameter.getParameterName());
return om.readValue(node.toString(), parameter.getParameterType());
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) webRequest.getAttribute(JSONBODYATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
if (jsonBody==null){
try {
jsonBody = IOUtils.toString(servletRequest.getInputStream());
webRequest.setAttribute(JSONBODYATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return jsonBody;
}
}
What's the different:
I'm using Jackson to convert json
I don't need a value in the annotation, you can read the name of the
parameter out of the MethodParameter
I also read the type of the parameter out of the Methodparameter => so the solution should be generic (i tested it with string and DTOs)
BR
Not sure where you add the json but if i do it like this with angular it works without the requestBody:
angluar:
const params: HttpParams = new HttpParams().set('str1','val1').set('str2', ;val2;);
return this.http.post<any>( this.urlMatch, params , { observe: 'response' } );
java:
#PostMapping(URL_MATCH)
public ResponseEntity<Void> match(Long str1, Long str2) {
log.debug("found: {} and {}", str1, str2);
}
You can also use a MultiValue Map to hold the requestBody in.
here is the example for it.
foosId -> pathVariable
user -> extracted from the Map of request Body
unlike the #RequestBody annotation when using a Map to hold the request body we need to annotate with #RequestParam
and send the user in the Json RequestBody
#RequestMapping(value = "v1/test/foos/{foosId}", method = RequestMethod.POST, headers = "Accept=application"
+ "/json",
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE ,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public String postFoos(#PathVariable final Map<String, String> pathParam,
#RequestParam final MultiValueMap<String, String> requestBody) {
return "Post some Foos " + pathParam.get("foosId") + " " + requestBody.get("user");
}
Use an inner class
#RestController
public class MyController {
#PutMapping("/do-thing")
public void updateFindings(#RequestBody Bodies.DoThing body) {
...
}
private static class Bodies {
public static class DoThing {
public String name;
public List<String> listOfThings;
}
}
}
request parameter exist for both GET and POST ,For Get it will get appended as query string to URL but for POST it is within Request Body
Good.
I suggest creating a Value Object (Vo) that contains the fields you need. The code is simpler, we do not change the functioning of Jackson and it is even easier to understand.
Regards!
You can achieve what you want by using #RequestParam. For this you should do the following:
Declare the RequestParams parameters that represent your objects and set the required option to false if you want to be able to send a null value.
On the frontend, stringify the objects that you want to send and include them as request parameters.
On the backend turn the JSON strings back into the objects they represent using Jackson ObjectMapper or something like that, and voila!
I know, its a bit of a hack but it works! ;)
you can also user #RequestBody Map<String, String> params,then use params.get("key") to get the value of parameter
If somebody is interested in the webflux solution, below is a reactive version, based on Biju answer.
Please note that there is one very small but synchronized chunk, needed to protect the body from being consumed more than once. If you prefer a fully non-blocking version, I suggest publishing the flux that obtains json on the same scheduler, to make checking and reading sequential.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
#Slf4j
#RequiredArgsConstructor
public class JsonArgumentResolver implements HandlerMethodArgumentResolver {
private static final String ATTRIBUTE_KEY = "BODY_TOSTRING_RESOLVER";
private final ObjectMapper objectMapper;
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArgument.class);
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
String fieldName = parameter.getParameterName();
Class<?> clz = parameter.getParameterType();
return getRequestBody(exchange).map(body -> {
try {
JsonNode jsonNode = objectMapper.readTree(body).get(fieldName);
String s = jsonNode.toString();
return objectMapper.readValue(s, clz);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
});
}
private Mono<String> getRequestBody(ServerWebExchange exchange) {
Mono<String> bodyReceiver;
synchronized (exchange) {
bodyReceiver = exchange.getAttribute(ATTRIBUTE_KEY);
if (bodyReceiver == null) {
bodyReceiver = exchange.getRequest().getBody()
.map(this::convertToString)
.single()
.cache();
exchange.getAttributes().put(ATTRIBUTE_KEY, bodyReceiver);
}
}
return bodyReceiver;
}
private String convertToString(DataBuffer dataBuffer) {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return new String(bytes, StandardCharsets.UTF_8);
}
}
I'm making simple REST service and the client for it. I try to make some kind of security, so I generate UUID just, when you go to /login site:
#RequestMapping("/login")
public uuid getUUID()
{
temp = new uuid();
return temp;
}
Then by the client side I get this UUID. Now I want to pass this UUID to my service "getPerson" which looks like this:
#RequestMapping("/{userId}/getperson")
public Person getPerson(#PathVariable("userId") int user, uuid uuid)
{
if(System.currentTimeMillis() - uuid.getDate().getTime() < 60000 &&
uuid.getHash().toString().equals(temp.toString()))
return personService.getPerson(user);
else
return null;
}
What I wanted to achieve is a simple validation of UUID by comparing its timestamps and Strings with the previously made temp object. And here is my problem - I don't know how to pass the object uuid from client.
My very sophisticated client looks like this:
RestTemplate restTemplate = new RestTemplate();
uuid myUUID = restTemplate.getForObject("http://localhost:8080/login", uuid.class);
HttpEntity<uuid> requestUUID = new HttpEntity<uuid>(myUUID);
//HttpEntity<Person> request = new HttpEntity<Person>(new Person("John", "Great", 2));
//restTemplate.postForObject("http://localhost:8080/addperson", request, Person.class);
Person person = restTemplate.postForObject("http://localhost:8080/2/getperson", requestUUID, Person.class);
I don't know if this type of validation is safe, but would be great if you could tell me how to pass my object.
I found simple solution below,
It is the concept which i shown below
import java.util.UUID;
public class Person {
private UUID uuid;
public Person() {
}
public Person(UUID uuid) {
super();
this.uuid = uuid;
}
public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
#Override
public String toString() {
return "Person [uuid=" + uuid + "]";
}
}
Controller IWebExample Interface:
import java.util.UUID;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
public interface IWebExample {
#RequestMapping(value = "/login", method = RequestMethod.GET)
#ResponseBody
UUID getUUID();
#RequestMapping(value = "/{userId}/getPerson", method = RequestMethod.POST)
#ResponseBody
Person getPerson(int userId, UUID uuid);
}
Controller Implementation
import java.util.UUID;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import com.finvendor.serverwebapi.resources.example.IWebExample;
import com.finvendor.serverwebapi.resources.example.Person;
#Controller
public class WebExampleImpl implements IWebExample {
#Override
public UUID getUUID() {
return UUID.randomUUID();
}
#Override
public Person getPerson(#PathVariable("userId") int userId, #RequestBody UUID uuid) {
return new Person(uuid);
}
}
Client code:
import java.util.UUID;
import org.springframework.web.client.RestTemplate;
public class ClientMainApp {
public static void main(String[] args) {
RestTemplate template=new RestTemplate();
UUID uuid = template.getForObject("http://localhost:8080/mylogin",UUID.class);
System.out.println("UUID="+uuid);
//here i am passing uuid object (as per your requirement)
Person person = template.postForObject("http://localhost:8080/1/getPerson", uuid, Person.class);
System.out.println(person);
}
}
Step to run
-Create war file
-Deploy in tomcat and
-Run the client code
Output would be:
UUID=e1e89e96-8118-446c-900d-d123b1b566ea
Person [uuid=e1e89e96-8118-446c-900d-d123b1b566ea]
In output you can observe we pass object uuid in postForObject(...) rest template method in client code.
The bottom line of solution of your problem is you need to use #RequestBody for uuid input param
Hope you got the idea and solution!!
I have created a Spring Boot with Spring REST application.
This is my controller code.
#RestController
public class SampleController {
#RequestMapping(value = "/sample/get", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public Response getResponse(SampleDTO dto) {
Response response = new Response();
response.setResponseMsg("Hello "+dto.getFirstName());
return response;
}
}
This is my SampleDTO
public class SampleDTO {
#JsonProperty("firstname")
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
and this is my Response object
public class Response {
private String responseMsg;
public String getResponseMsg() {
return responseMsg;
}
public void setResponseMsg(String responseMsg) {
this.responseMsg = responseMsg;
}
}
When I try to access service this way
http://localhost:8080/sample/get?firstName=mvg
I am getting this expected output
{"responseMsg":"Hello mvg"}
When I try to access service this way
http://localhost:8080/sample/get?firstname=mvg
I am getting this output
{"responseMsg":"Hello null"}
My question is how do I map 'firstname' in request parameter with 'firstName' of DTO?
Thanks in advance
When you are setting #JsonProperty("firstname") make sure you are imported the this statement "import com.fasterxml.jackson.annotation.JsonProperty ;". One thing more if you are sending more properties and your bean class does not have that you can set the #JsonIgnoreProperties(ignoreUnknown=true) on the top of the bean name.
You are also missing the #RequestBody annotation. You should take this as (#RequestBody SampleDTO dto)in getResponse method.
Simply create a Pojo Java Bean with fields with names that match your request parameters.
Then use this class as an argument for your request handler method (without any additional annotations)
see this
First you need to choose which approach do you need (or want to use) param or model. When you use something like this http://localhost:8080/sample/get?firstName=mvg , you are passing data as request parameters. So you need to use the #RequestParam annotation.
Sample using #RequestParam annotation (use is explained in the docs)
#RequestMapping(method = RequestMethod.GET)
public Response foo(#RequestParam("firstName") String firstName) {
Response response = new Response();
response.setResponseMsg("Hello "+ firstName );
return response;
}
My Spring Controller of Spring JSON application returns a JSONObject. On accessing the url, i am getting 406 error page.
It works when i return String or ArrayList.
Spring Controller:
package com.mkyong.common.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONException;
import org.json.JSONObject;
#Controller
public class JSONController {
#RequestMapping("/test")
#ResponseBody
public JSONObject test() {
try {
JSONObject result = new JSONObject();
result.put("name", "Dade")
.put("age", 23)
.put("married", false);
return result;
} catch (JSONException ex) {
Logger.getLogger(JSONController.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}
How can i resolve this issue? Thanks for help. I am new to Spring MVC, couldn't found resolution to this issue in the existing SO answers.
You're trying to manually do something that Spring MVC already it automatically for you. Spring automatically deduces a representation of the returning type and does a converstion. How it does it you can learn from http://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc. In your case its converting to JSON.
It works when i return String or ArrayList
What happens under the hood is that Spring MVC is using Jackson library, to convert the return type to JSON. And since it has no issue converting the String or List type, all works OK.
What happens in the code you've posted is that, Jackson's object mapper is trying to convert JSONObject instance to JSON, and this fails, cause jackson expects a POJO object which JSONObject instance isn't.
To have it work you should simply write your POJO and return it. So something like
public class Person {
private String name;
private Integer age;
private Boolean married;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getMarried() {
return married;
}
public void setMarried(Boolean married) {
this.married = married;
}
}
and have your method changed to
#RequestMapping("/test")
#ResponseBody
public Person test() {
Person person = new Person();
person.setName("Dade");
person.setAge(23);
person.setMarried(false);
return person;
}
For what concerns your error, the same exception you will see in the working example if you for example delete getters and setters, or name them wrongly, an exception happens while trying to convert to a representation and you get a 406 error
I think you need to set headers in #RequestMapping and return HashMap.
#RequestMapping(value = "json", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody
Map<String, String> helloJson() {
HashMap<String, String> map = new HashMap<String, String>();
map.put("k1", "v1");
map.put("k2", "v2");
map.put("k3", "v3");
return map;
}
I have a Spring MVC project and I have configured the jackson library to automatically transform the response (java object) to a json and it works in GET requests as following.
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody Orders createOrder(Model model){
Orders orders = new Orders();
//Populate orders.....
return orders;
}
But when a I try to process the POST request and get the object from the json's request, I get the error "400 Bad Request" as Spring cannot create the object Orders from the json. I put the same json file that the GET method response, so I suppose that the file is well formatted.
#RequestMapping(method = RequestMethod.POST)
public #ResponseBody ResponseEntity<String> createOrder(#RequestBody Orders orders){
LOG.info(orders.toString());
return new ResponseEntity<String>("", HttpStatus.CREATED);
}
If I change the #RequestBody class to String (createOrder(#RequestBody String orders)), the POST request is well processed.
Do I have to create a mapper that maps the input json to the class Order?
UPDATE:
I have created the most simple example to try it and I receive the error 400. Exmaple:
Domain: Home.java
public class Home {
private String address = "Calle mármoles";
public Home(){
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Controller:
#RequestMapping(method = RequestMethod.POST)
public #ResponseBody ResponseEntity<String> createOrder2(#RequestBody Home orders){
return new ResponseEntity<String>("{}", HttpStatus.CREATED);
}
JSON (POST):
{
address: "Prueba"
}
[SOLVED]
I missed to put "" in the name of the parameter name.
[SOLVED]
I missed to put "" in the name of the parameter name.
Besides, the class has to have a default blank constructor in order to allow the jackson library creating the object.