Jackson failed to convert request element - spring

I'm using Spring to craft a REST API which exposes a POST endpoint. I'm able to reach the endpoint, but I'm having trouble reading the request body.
The following code works: payload contains the object sent.
#RestController
public class RestController {
#RequestMapping(value = "/endpoint")
public ResponseEntity endpoint(#RequestParam("payload") String str) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
Payload payload = objectMapper.readValue(str, Payload.class);
return ResponseEntity.status(HttpStatus.OK).build();
}
}
However, the following code DOES NOT works: it throws an exception.
#RestController
public class RestController {
#RequestMapping(value = "/endpoint")
public ResponseEntity endpoint(#RequestParam("payload") Payload payload) throws IOException {
return ResponseEntity.status(HttpStatus.OK).build();
}
}
The exception:
Failed to convert request element: org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'beans.Payload'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'beans.Payload': no matching editors or conversion strategy found
Why the latter doesn't work? I thought Spring decode request parameters in the same way...
UPDATE: my Payload class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Payload {
#JsonProperty("type")
private String type;
#JsonProperty("callback_id")
private String callbackId;
#JsonProperty("message_ts")
private String message_ts;
#JsonProperty("response_url")
private String responseUrl;
protected Payload() {}
public String getType() {
return type;
}
public String getCallbackId() {
return callbackId;
}
public String getMessage_ts() {
return message_ts;
}
public String getResponseUrl() {
return responseUrl;
}
}
UPDATE: I'm testing the endpoint with Postman. This is what I'm sending:
KEY VALUE
payload { "type": "test" }
and this is the error I got:
{
"timestamp": "2018-08-28T10:38:33.133+0000",
"status": 415,
"error": "Unsupported Media Type",
"message": "Content type 'multipart/form-data;boundary=--------------------------586093407866564427326096;charset=UTF-8' not supported",
"path": "/endpoint"
}

Payload constructor should not be protected. It should be public.
Change it to
Public PayLoad(){}
Otherwise, controllers cant create a payload object when mapping is done.

Use #RequestBody instead of #RequestParam. Your Payload is in post body and #RequestBody annotation will deserialize it to payload.

Just get rid of protected Payload() {} . As you don't have any parameterized constructer you are fine, Java compiler will take care of adding the default constructer to the compiled byte code.
And you need to change this
Controller method
#RestController
public class RestController {
#RequestMapping(value = "/endpoint")
public ResponseEntity endpoint(#RequestParam("payload") Payload payload) throws IOException {
return ResponseEntity.status(HttpStatus.OK).build();
}
}
Changes
a. Change HTTP request method to POST it, instead of GET . (method = RequestMethod.POST) .
b. Change Payload to a message body insteda of request param (#RequestParam("payload") Payload payload --> #RequestBody Payload payload ).
Change it as
#RequestMapping(value = "/endpoint", method = RequestMethod.POST)
public ResponseEntity endpoint(#RequestBody Payload payload) throws IOException {
return ResponseEntity.status(HttpStatus.OK).build();
}

Your URL patterns are mal-configured. Try,
#RestController
#RequestMapping(value = "/")
public class PayLoadController {
#RequestMapping(value = "endpoint/",method = RequestMethod.POST)
public ResponseEntity endpoint(#RequestBody Payload payload) throws IOException {
return ResponseEntity.status(HttpStatus.OK).build();
}}
Just copy paste this code and rename controller file name.
Then post your data to,
localhost:8080/endpoint/

Related

Spring POST controller request body as controller variable

Usually, we get request body as a parameter of controller methods. Spring binds the body to the type of the variable.
I want the request body as a property of the controller so that other private methods can access it.
public class UserController {
private String body; // Request body should automatically bind to this String.
private HttpServletRequest request;
#Autowired
public UserController(HttpServletRequest request) {
this.request = request;
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<String> create(#RequestBody String body) {
// I know I can do like: this.body = body.
// But don't want to do that.
}
private someMethod() {
// Give me access of request body please....
}
Controllers are by default singleton scoped beans (created once per container initialisation). Assigning request body (which changes in every request) to something which is created once and can be used everywhere can lead you to serious trouble. If you are just looking to apply some logic in controller by using private method, you can pass the body as argument to that method like this.
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<String> create(#RequestBody String body) {
someMethod(body);
}
private void someMethod(String body) {
// voila! you have the request body here
}

Why do methods on #RestControllerAdvice class return HTML instead of JSON?

I have the following exception handler:
#RestControllerAdvice
#RequiredArgsConstructor
public class ControllerExceptionHandler {
#ExceptionHandler(FeignException.class)
#ResponseBody
public String afterThrowing(FeignException ex, HttpServletResponse response) {
response.setStatus(ex.status());
return ex.contentUTF8();
}
}
I would expect when the FeignException propagates to one of my REST controllers that
The afterThrowing method would be called
The response returned to an HTTP client would be JSON
The method is called but the content type returned to the client is HTML instead of JSON. How can I have JSON returned instead of HTML?
You should wrap your response with something(class or map).
Wrapper class :
public class ApiError {
private HttpStatus status;
private String response;
public ApiError(String response, HttpStatus status) {
this.response = s;
this.status = status;
}
// getter setter
}
And exception handler :
#ExceptionHandler(FeignException.class)
protected ResponseEntity<Object> handleFeignException(FeignException ex) {
ApiError apiError = new ApiError(ex.contentUTF8(), NOT_ACCEPTABLE);
return new ResponseEntity<>(apiError, apiError.getStatus());
}
For further reading you can check this question : How to return simple String as JSON in Rest Controller
Edit:
Since your ex.contentUTF8() call returns valid JSON you don't need to wrap it. Simply return String with ResponseEntity.
#ExceptionHandler(FeignException.class)
protected ResponseEntity<String> handleFeignException(FeignException ex) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(ex.contentUTF8(), headers, BAD_REQUEST);
}

Setting a default content-type header while using #RequestBody

When using #Requestbody, I am not able to run the API without sending a content-type header with a value of application/json. I want a method to set this header by default whenever the controller is called.
I don't want to use HttpRequest. I have tried to set the produces and consumes parameter in #RequestMapping, but in vain. I noticed that without the header the user is not able to hit the API.
#ResponseBody
#PostMapping(value = "/1.0/{productType}/itinerary/create)
protected ResponseEntity<String> createIternary(#PathVariable final String productType,
#RequestParam(name = "product-id", required = false, defaultValue = "-1") String productIdStr,
#RequestParam Map<String, String> searchRequest)
throws Exception {}
{
"timestamp": 1568117108917,
"status": 415,
"error": "Unsupported Media Type",
"exception": "org.springframework.web.HttpMediaTypeNotSupportedException",
"message": "Content type 'text/plain' not supported",
"path": "/local/1.0/ttd/itinerary/create"
}
//This is the error in Postman
{
"timestamp":1568116888056,
"status":415,
"error":"Unsupported Media Type",
"exception":
"org.springframework.web.HttpMediaTypeNotSupportedException",
"message":"Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported",
"path":"/local/1.0/ttd/itinerary/create"
}
//This is the error in terminal
Spring dependends on Content-Type header to correctly handle the request.
If you want to customize it, you need to do it yourself, by writing some sort of request wrapper and / or a filter.
UPDATE
You'll need to create a class which extends from HttpServletRequestWrapper and a filter.
The HttpServletRequestWrapper can be used exactly for that. Sometimes you want to adjust the original request. With this wrapper, you can wrap the original request and override some methods to modify it's behaviour.
ContentTypeRequestWrapper.java
public class ContentTypeRequestWrapper extends HttpServletRequestWrapper {
public ContentTypeRequestWrapper(HttpServletRequest request) {
super(request);
}
#Override
public String getContentType() {
return "application/json";
}
#Override
public String getHeader(String name) {
if (name.equalsIgnoreCase("content-type")) {
return "application/json";
}
return super.getHeader(name);
}
#Override
public Enumeration <String> getHeaderNames() {
List <String> headerNames = Collections.list(super.getHeaderNames());
if (!headerNames.contains("content-type")) {
headerNames.add("content-type");
return Collections.enumeration(headerNames);
}
return super.getHeaderNames();
}
#Override
public Enumeration <String> getHeaders(String name) {
if (name.equalsIgnoreCase("content-type")) {
return Collections.enumeration(Collections.singletonList("application/json"));
}
return super.getHeaders(name);
}
}
ForcedApplicationJsonContentTypeFilter.java
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ForcedApplicationJsonContentTypeFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException,
ServletException {
chain.doFilter(new ContentTypeRequestWrapper((HttpServletRequest) req), resp);
}
}
Of course this is not the best way to do so. The way I wrote it, you're assuming that 100% of the incoming request to your API are in JSON format.
You'll probably need to adapt the ContentTypeRequestWrapper to do some validations like 'is Content-Type already set? Then don't override'.
The #PostRequest annotation has both a consumes and produces property. In this case you would want to use the produces property:
#PostMapping(value = "/1.0/{productType}/itinerary/create", produces = "application/json")

Make Spring's #RequestBody annotation return a custom response on-fail

I have a controller as follows:
public void createEntity(#Valid #RequestBody final MyEntity myEntity) {}
However, when the object transformation fails, the API automatically returns a 400 with the Java stack trace. How can I modify this on-failure response? (I wish to change or remove the response message).
Here is an example how to do this with an #ExceptionHandler annotation
#RestController
public class Controller {
#RequestMapping(value = "/", method = RequestMethod.POST)
public void createEntity(#Valid #RequestBody final MyEntity myEntity) {
//
}
#ControllerAdvice
public class RestEndpointExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleNotValidExceptionException(HttpServletRequest req, MethodArgumentNotValidException ex) {
Object customException = "Validation failed";
return new ResponseEntity<Object>(customException, HttpStatus.BAD_REQUEST);
}
}
}
I pushed the code in here
You can use #ExceptionHandler with #ResponseStatus and leave handler empty so that only Status Code is returned back.
#ExceptionHandler(EmptyResultDataAccessException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public void notFoundException() {
}

How to access plain json body in Spring rest controller?

Having the following code:
#RequestMapping(value = "/greeting", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#ResponseBody
public String greetingJson(#RequestBody String json) {
System.out.println("json = " + json); // TODO json is null... how to retrieve plain json body?
return "Hello World!";
}
The String json argument is always null despite json being sent in the body.
Note that I don't want automatic type conversion, I just want the plain json result.
This for example works:
#RequestMapping(value = "/greeting", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#ResponseBody
public String greetingJson(#RequestBody User user) {
return String.format("Hello %s!", user);
}
Probably I can use the use the ServletRequest or InputStream as argument to retrieve the actual body, but I wonder if there is an easier way?
Best way I found until now is:
#RequestMapping(value = "/greeting", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#ResponseBody
public String greetingJson(HttpEntity<String> httpEntity) {
String json = httpEntity.getBody();
// json contains the plain json string
Let me know if there are other alternatives.
You can just use
#RequestBody String pBody
Only HttpServletRequest worked for me. HttpEntity gave null string.
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
#RequestMapping(value = "/greeting", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#ResponseBody
public String greetingJson(HttpServletRequest request) throws IOException {
final String json = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
System.out.println("json = " + json);
return "Hello World!";
}
simplest way that works for me is
#RequestMapping(value = "/greeting", method = POST, consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public String greetingJson(String raw) {
System.out.println("json = " + raw);
return "OK";
}
If you have dozens of Methods that need to get HTTP body as JSON and convert it to custom data type, it is a better way to implement the support on the framework
public static class Data {
private String foo;
private String bar;
}
//convert http body to Data object.
//you can also use String parameter type to get the raw json text.
#RequestMapping(value = "/greeting")
#ResponseBody
public String greetingJson(#JsonBody Data data) {
System.out.println(data);
return "OK";
}
notice that we using user defined annotation #JsonBody.
// define custom annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface JsonBody {
String encoding() default "utf-8";
}
//annotation processor for JsonBody
#Slf4j
public class JsonBodyArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(JsonBody.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
JsonBody annotation = parameter.getParameterAnnotation(JsonBody.class);
assert annotation != null;
ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class);
if (servletRequest == null) {
throw new Exception("can not get ServletRequest from NativeWebRequest");
}
String copy = StreamUtils.copyToString(servletRequest.getInputStream(), Charset.forName(annotation.encoding()));
return new Gson().fromJson(copy, parameter.getGenericParameterType());
}
}
// register the annotation processor
#Component
public class WebConfig implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new JsonBodyArgumentResolver());
}
}
As of 4.1 you can now use RequestEntity<String> requestEntity and access the body by requestEntity.getBody()
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/RequestEntity.html

Resources