I am calling an endpoint which returns JSON that that looks something like this (in Postman):
{
"Result": {
"attribute1": { ... },
"attribute2": { ... }
}
}
The Content-Type header returned by this request is text/x-json (as opposed to the usual application/json). I think this is causing some problems when trying to deserialize this through Jackson. The POJO for this JSON looks something like this:
#Getter
#Setter
public class Response {
#JsonProperty("Result")
private Result result;
}
The Result class is from an external library (the same guys who wrote this endpoint). Either ways, when I try to call this endpoint through RestTemplate.exchange(), Jackson is unable to deserialize this JSON into a valid Result class. I am doing this:
ResponseEntity<Response> response = restTemplate.exchange(url, HttpMethod.GET, null, Response.class);
Doing response.getBody() gives a Response object which contains a null Result object. Apparently, Jackson is not deserializing the JSON properly. I suspect this is because of the unusual text/x-json Content-Type returned by the API.
I also have my MappingJackson2HttpMessageConverter object configured to be able to parse text/x-json Content-type, but no luck:
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setSupportedMediaTypes(ImmutableList.of(new MediaType("text", "x-json")));
restTemplate.getMessageConverters().add(jsonConverter);
Any pointers?
Update: I don't know why this didn't work, but I figured out an alternative way - fetching the JSON as Map instead of a domain object, which is good enough for my purposes.
By default MappingJackson2HttpMessageConverter is bind to:
application/json
application/*+json
We need to add text/x-json.
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
List<MediaType> jsonTypes = new ArrayList<>(jsonConverter.getSupportedMediaTypes());
jsonTypes.add(new MediaType("text", "x-json"));
jsonConverter.setSupportedMediaTypes(jsonTypes);
Now, we should use it in RestTemplate:
restTemplate.setMessageConverters(Collections.singletonList(jsonConverter));
ResponseEntity<RequestPayload> response = restTemplate.exchange(url, HttpMethod.GET, null, RequestPayload.class);
Related
I'm trying to return a ResponseEntity list and casting that response respect to my model classes.
For instance: if I use ResponseEntity<List<ApplicationModel>> it's working well but I do not want to write a response method per model.
ResponseEntity method
public static <T> ResponseEntity<List<T>> getResponseList(String resourceURL) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<List<T>> entity = new HttpEntity<List<T>>(headers);
ResponseEntity<List<T>> response = restTemplate.exchange(resourceURL, HttpMethod.GET, entity,
new ParameterizedTypeReference<List<T>>() {
}, Collections.emptyMap());
return response;
}
Method call
private final String url ="http://localhost:8090/xxx/application";
ResponseEntity<List<ApplicationModel> responseForApplications =
ResponseTemplate.getResponseList(url);
if (responseForApplications.getStatusCode() == HttpStatus.OK)
List<ApplicationModel> dtoApplications = responseForApplications.getBody();
Example of JSON response which I want to cast
{"id":1,"name":"foo","description":"foo"}
Error
There was an unexpected error (type=Internal Server Error, status=500).
Error creating bean with name 'index': Invocation of init method failed; nested exception is java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.xxx.ApplicationModel
The issue's coming from Jackson. When it doesn't have enough
information on what class to deserialize to, it uses LinkedHashMap.
Since you're not informing Jackson of the element type of your
ArrayList, it doesn't know that you want to deserialize into an
ArrayList of ApplicationModels. So it falls back to the default.
Instead, you could probably use as(JsonNode.class), and then deal with
the ObjectMapper in a richer manner than rest-assured allows.
Please refer to java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.testing.models.Account for additional information.
Using mapper.convertValue works for me.
ObjectMapper mapper = new ObjectMapper();
List<ApplicationModel> dtoApplications = mapper.convertValue(responseForApproles.getBody(), new TypeReference<List<ApplicationModel>>() {});
I have a POJO of the form:
#Data
public class BaseRequest {
private String type;
private Map<String, Object> details;
private Map<String, Object> signature;
}
I have a service running which only accepts Content Type: "application/x-www-form-urlencoded".
I have written a client in Java which uses Spring's RestTemplate to make calls.
public String getInvoice(BaseRequest req, String url) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<BaseRequest> httpEntity = new HttpEntity<BaseRequest>(req, headers);
String response = this.restTemplate.postForObject(url, httpEntity, String.class);
return response;
}
However, it throws an error:
org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.x.y.z.BaseRequest] and content type [application/x-www-form-urlencoded]
It works if I set the content type as JSON:
headers.setContentType(MediaType.APPLICATION_JSON);
I know it works for JSON because I have configured my RestTemplate Bean with JacksonHTTPMessageConverter. So I can easily convert POJOs to application/json. However, I am not able to figure out how to do that with application/x-www-form-urlencoded.
I've been searching this for awhile now, and the only solution which I've found is to write my own converter to convert my BaseRequest class to Spring's MultiValueMap, and then Spring's FormHttpMessageConverter will automatically handle it. But I want to avoid doing that. Is there any other way around this?
Any leads would be appreciated. Thanks!
EDIT:
My question is different from #JsonProperty not working for Content-Type : application/x-www-form-urlencoded. The conversion happening there is about accepting data in application/x-www-form-urlencoded and converting it to a POJO. My question is about converting a POJO to application/x-www-form-urlencoded while using Spring's resttemplate to make calls. And like I mentioned, I know I can achieve this by writing my own converter to convert my POJO to Spring's MultiValueMap. However, I want to know if I can avoid doing this.
EDIT:
Dump of $_POST on the API when I send my data as MultiValueMap<String, Object>:
"array(0) {
}"
Dump of $_POST on the API when I send my data through Postman in the correct format:
"array(2) {
["type"]=>
string(16) "abcd"
["details"]=>
array(1) {
["template_file"]=>
string(16) "x.html"
}
}"
Try to convert your nested object in request payload to the org.springframework.util.MultiValueMap. Add and implement converter method in your POJO
public class BaseRequest {
// ...
public MultiValueMap<String, Object> toMap() {
MultiValueMap<String, Object> result = new LinkedMultiValueMap<>();
result.add("type", type);
result.put("details", details);
result.put("signature", signature);
return result;
}
}
Now use it during request creation
HttpEntity<BaseRequest> httpEntity = new HttpEntity<BaseRequest>(req.toMap(), headers);
That is caused because inside FormHttpMessageConverter which performs actual conversion method canRead(Class<?>, MediaType) checks if MultiValueMap.class.isAssignableFrom(clazz) where clazz is your payload object. In your case it failed, so FormHttpMessageConverter skipped.
Hope it helps!
#POST
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
#Path("/data/services")
public Response DiscoverDevice(BlockDevsPost blockdevice) {
for (DeviceIdentifier device : blockdevice.getDevice()) {
String dev = device.Device();
System.out.println("DEVICE "+ dev);
if (dev == null || dev.equals("")){
return Response.status(Response.Status.BAD_REQUEST).entity("Device cannot be null or empty.").build();
}
}
}
Getting this error when fired POST from REST Client when dev is null. I am not able to get JSON and this error is thrown:
Unexpected character (D) at position 0. Device Identifier cannot be null or empty.
Where D in Device Identifier marked as Red which means it is not returning JSON as response.
Your client is expecting to get JSON but you have set a plain string in the Response entity and application/json as content-type. You need to return a valid JSON. For example
return Response
.status(Response.Status.BAD_REQUEST)
.entity("{\"error\":\"Device cannot be null or empty.\"}")
.build();
You can also build the json response string using your preferred mapper (you will need to add a dependency). This is an example using Jackson
Jackson using API
ObjectMapper mapper = new ObjectMapper();
ObjectNode objectNode = mapper.createObjectNode();
objectNode.put("error", "Device cannot be null or empty.");
String json = mapper.writeValueAsString(objectNode);
Jackson using POJO
class ErrorBean{
private String error;
//getters and setters
}
ObjectMapper mapper = new ObjectMapper();
ErrorBeanerrorBean = new ErrorBean();
errorBean.setError ("Device cannot be null or empty.");
String json = mapper.writeValueAsString(errorBean);
You can also return POJO from your service method and let the JAX-RS implementation to convert them to JSON (this means change the response type). See https://www.mkyong.com/webservices/jax-rs/json-example-with-jersey-jackson/
Spring allows the definition of #ExceptionHandlers inside of #RestControllerAdvice.
I already defined a lot of other ExceptionHandlers in there for HTTP 400, 404, 405,... However the ExceptionHandler for HTTP 406 (NOT_ACCEPTABLE) does not seem to work. The handler is triggered, I checked that in the logs, but the result is not used.
My goal is it to return a HTTP 406 with a JSON body.
Variant 1
#ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
#ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public ErrorDTO requestMethodNotSupported(final HttpMediaTypeNotAcceptableException e) {
final ErrorDTO dto = new ErrorDTO(HttpStatus.NOT_ACCEPTABLE, "http.media_not_acceptable");
return dto;
}
Variant 2
#ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public ResponseEntity<ErrorDTO> requestMethodNotSupported2(final HttpMediaTypeNotAcceptableException e) {
final ErrorDTO dto = new ErrorDTO(HttpStatus.NOT_ACCEPTABLE, "http.media_not_acceptable");
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).contentType(MediaType.APPLICATION_JSON_UTF8).body(dto);
}
But I always get a HTML response similar to this from the Tomcat:
HTTP Status 406 -
type: Status report
message:
description: The resource identified by
this request is only capable of generating responses with
characteristics not acceptable according to the request "accept"
headers.
instead of
{
"errorCode": 406,
"errorMessage": "http.media_not_acceptable"
}
Request-Headers:
Accept: application/stuff-that-cannot-be-present
Actual-Response-Headers:
Content-Type: text/html
Expected-Response-Headers:
Content-Type: application/json
I know that I could simply "fix" the Accept-Header that is send by the client, however the server should always respond in JSON, if it does not know how to respond.
I use Spring 4.3.3.RELEASE and Jackson 2.8.4.
Finally I found a solution for this:
Instead of returning a serializable object just return the bytes directly.
private final ObjectMapper objectMapper = new ObjectMapper();
#ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public ResponseEntity<byte[]> mediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException e) {
Object response = ...;
try {
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(objectMapper.writeValueAsBytes(response));
} catch (Exception subException) {
// Should never happen!!!
subException.addSuppressed(e);
throw subException;
}
}
EDIT:
As an alternative you can create a custom HttpMessageConverter<ErrorResponse> for your ErrorResponse object.
Go to your or create a impl of WebMvcConfigurerAdapter#extendMessageConverters(converters)
Pick a HttpMessageConverter that is capable of creating your expected result/content type.
Wrap it in a way to fulfill the following conditions:
getSupportedMediaTypes() returns MediaType.ALL
canRead() returns false
canWrite()returns only true for your ErrorResponse
write() sets the forced CT and forward your expected content type to the wrapped converter.
Add your wrapper to the converters list.
If added as first element then it will always return your expected result (forced)
Requested: json , Returned: forced CT
Requested: xml , Returned: forced CT
Requested: image , Returned: forced CT
If added as last element then it will only return the result as your expected result, if there was no other matching converter (fallback)
Requested: json , Returned: json
Requested: xml , Returned: xml
Requested: image , Returned: forced CT
Building on #ST-DDT findings. If you are also extending ResponseEntityExceptionHandler then you cannot just add another method to handle HttpMediaTypeNotAcceptableException. However, there is an even simpler solution to the whole problem then:
#Override
public ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ResponseEntity<Object> response = super.handleHttpMediaTypeNotAcceptable(ex, headers, status, request);
// Workaround to return JSON response for 406
return ResponseEntity.status(NOT_ACCEPTABLE)
.contentType(APPLICATION_JSON)
.body(response.getBody());
}
I struggled with an extrange spring behavior using RestTemplate (org.springframework.web.client.RestTemplate) without success.
I use in my hole application below code and always receive an XML response, which I parse and evaluate its result.
String apiResponse = getRestTemplate().postForObject(url, body, String.class);
But can't figure out why a server response is in JSON format after executing:
String apiResponse = getRestTemplate().getForObject(url, String.class);
I've debugged at low level RestTemplate and the content type is XML, but have no idea why the result is in JSON.
When I access from a browser the response is also in XML, but in apiResponse I got JSON.
I tried many options after reading Spring documentation
http://docs.spring.io/spring/docs/3.0.x/api/org/springframework/web/client/RestTemplate.html
Also tried to modify explicitly the headers but still can't figure it out.
I debugged RestTemplate class and noticed that this method is always setting application/json:
public void doWithRequest(ClientHttpRequest request) throws IOException {
if (responseType != null) {
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
if (messageConverter.canRead(responseType, null)) {
List<MediaType> supportedMediaTypes = messageConverter.getSupportedMediaTypes();
for (MediaType supportedMediaType : supportedMediaTypes) {
if (supportedMediaType.getCharSet() != null) {
supportedMediaType =
new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
}
allSupportedMediaTypes.add(supportedMediaType);
}
}
}
if (!allSupportedMediaTypes.isEmpty()) {
MediaType.sortBySpecificity(allSupportedMediaTypes);
if (logger.isDebugEnabled()) {
logger.debug("Setting request Accept header to " + allSupportedMediaTypes);
}
request.getHeaders().setAccept(allSupportedMediaTypes);
}
}
}
Could you give an idea?
I could solve my issue with RC.'s help. I'll post the answer to help other people.
The problem was that Accept header is automatically set to APPLICATION/JSON so I had to change the way to invoke the service in order to provide the Accept header I want.
I changed this:
String response = getRestTemplate().getForObject(url, String.class);
To this in order to make the application work:
// Set XML content type explicitly to force response in XML (If not spring gets response in JSON)
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_XML));
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
ResponseEntity<String> response = getRestTemplate().exchange(url, HttpMethod.GET, entity, String.class);
String responseBody = response.getBody();