Paul Croarkin
Paul Croarkin

Reputation: 14675

Spring RestTemplate Parse Custom Error Response

Given a REST service call

http://acme.com/app/widget/123

returns:

<widget>
  <id>123</id>
  <name>Foo</name>
  <manufacturer>Acme</manufacturer>
</widget>

This client code works:

RestTemplate restTemplate = new RestTemplate();
XStreamMarshaller xStreamMarshaller = new XStreamMarshaller();
xStreamMarshaller.getXStream().processAnnotations(
    new Class[] { 
        Widget.class,
        ErrorMessage.class
    });

HttpMessageConverter<?> marshallingConverter = new MarshallingHttpMessageConverter(
    xStreamMarshaller, xStreamMarshaller);

List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
    converters.add(marshallingConverter);

restTemplate.setMessageConverters(converters);

Widget w = restTemplate.getForObject(
    "http://acme.com/app/widget/{id}", Widget.class, 123L);

However, calling http://acme.com/app/widget/456 returns:

<error>
    <message>Widget 456 does not exist</message>
    <timestamp>Wed, 12 Mar 2014 10:34:37 GMT</timestamp>
</error>

but this client code throws an Exception:

Widget w = restTemplate.getForObject(
    "http://acme.com/app/widget/{id}", Widget.class, 456L);

org.springframework.web.client.HttpClientErrorException: 404 Not Found

I tried:

try {
    Widget w = restTemplate.getForObject(
       "http://acme.com/app/widget/{id}", Widget.class, 456L);
}
catch (HttpClientErrorException e) {
    ErrorMessage errorMessage = restTemplate.getForObject(
       "http://acme.com/app/widget/{id}", ErrorMessage.class, 456L);

   // etc...
}

The second invocation just threw another HttpClientErrorException, plus it does not feel right calling the service twice.

Is there a way to call the service once and parse the response into a Widget on success and an ErrorMessage when not found?

Upvotes: 13

Views: 27259

Answers (4)

Rick P
Rick P

Reputation: 29

I too have found this a disturbing change in the Spring library. You used to be able to throw a ResponseStatusException and pass it the HttpStatus code and a custom message and then catch the HttpClientErrorException and simply print getMessage() to see the custom error. This no longer works. In order for me to print the custom error, I had to capture the ResponseBody as a String, getResponseBodyAsString on the HttpClientErrorException. Then I needed to parse this as a String doing some pretty hokey substring manipulation. Doing this strips out the information from the ResponseBody and gives the message sent by my server. The code to do this follows:

String message = hce.getResponseBodyAsString();
int start, end;
start =  message.lastIndexOf(":", message.lastIndexOf(",")-1) + 2;
end = message.lastIndexOf(",") -1;
message = message.substring(start, end);
System.out.println(message);

When I test this using ARC or Postman, they can display the correct message after I add server.error.include-message=always to my application.properties file on the server. I am not sure what method they are using to extract the message but that would be nice to know.

Upvotes: 1

zygimantus
zygimantus

Reputation: 3777

You can also create an object from Error response body if you like:

ObjectMapper om = new ObjectMapper();
String errorResponse = ex.getResponseBodyAsString();

MyClass obj = om.readValue(errorResponse, MyClass.class);

Upvotes: 8

vu le
vu le

Reputation: 361

As you already catch the HttpClientErrorException object, it should allows you to easily extract useful information about the error and pass that to the caller.

For example:

try{
    <call to rest endpoint using RestTemplate>
} catch(HttpClientErrorException e){
    HttpStatus statusCode = e.getStatusCode();
    String body = e.getResponseBodyAsString();
}

IMO if one needs to further de-serialize the error message body into some relevant object, that can be handled somewhere outside of the catch statement scope.

Upvotes: 2

nickdos
nickdos

Reputation: 8414

Following from my comment, I checked the HttpClientErrorException JavaDoc and it does support both setting/getting the statusText as well as the responseBody. However they are optional and RestTemplate may not populate them - you'll need to try something like:

try {
    Widget w = restTemplate.getForObject(
       "http://acme.com/app/widget/{id}", Widget.class, 456L);
}
catch (HttpClientErrorException e) {
    String responseBody = e.getResponseBodyAsString();
    String statusText = e.getStatusText();
    // log or process either of these...
    // you'll probably have to unmarshall the XML manually (only 2 fields so easy)
}

If they are both empty/null then you may have to extend the RestTemplate class involved and populate those fields yourself and/or raise a Jira issue on the Spring site.

Upvotes: 14

Related Questions