Spring RestTemplate exception handling
Asked Answered
H

17

168

Below is the code snippet. Basically, I am trying to propagate the exception when the error code is anything other than 200.

ResponseEntity<Object> response = restTemplate.exchange(
    url.toString().replace("{version}", version), 
    HttpMethod.POST, 
    entity, 
    Object.class
);

if (response.getStatusCode().value() != 200) {
    logger.debug("Encountered Error while Calling API");
    throw new ApplicationException();
}

However, in the case of a 500 response from the server I am getting the exception

org.springframework.web.client.HttpServerErrorException: 500 Internal Server Error
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94) ~[spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]

Do I really need to wrap the rest template exchange method in try? What would then be the purpose of codes?

Hodosh answered 29/6, 2016 at 7:46 Comment(1)
Kindly share the code of ApplicationException()Hesitancy
P
169

You want to create a class that implements ResponseErrorHandler and then use an instance of it to set the error handling of your rest template:

public class MyErrorHandler implements ResponseErrorHandler {
  @Override
  public void handleError(ClientHttpResponse response) throws IOException {
    // your error handling here
  }

  @Override
  public boolean hasError(ClientHttpResponse response) throws IOException {
     ...
  }
}

[...]

public static void main(String args[]) {
  RestTemplate restTemplate = new RestTemplate();
  restTemplate.setErrorHandler(new MyErrorHandler());
}

Also, Spring has the class DefaultResponseErrorHandler, which you can extend instead of implementing the interface, in case you only want to override the handleError method.

public class MyErrorHandler extends DefaultResponseErrorHandler {
  @Override
  public void handleError(ClientHttpResponse response) throws IOException {
    // your error handling here
  }
}

Take a look at its source code to have an idea of how Spring handles HTTP errors.

Pollinate answered 29/6, 2016 at 8:10 Comment(4)
I have 1 instance of RestTemplate that I reuse for different calls. I need to handle errors from different calls differently - apparently there is no way to do that with global handler - I need to provide a handler per request.Schmo
With this error handler I always get a ResourceAccessException because RestTemplate.doExecute catches IOExceptions and throws a ResourceAccessException. What am I doing wrong?Iridize
I was able to solve this by extending DefaultResponseErrorHandler.Infirm
@CrengutaS the DefaultResponseErrorHandler's handleError method is called, but ultimately the HttpClientErrorException gets propagated to the client. How have you handled that in your handler?Helaine
F
88

Spring cleverly treats http error codes as exceptions, and assumes that your exception handling code has the context to handle the error. To get exchange to function as you would expect it, do this:

    try {
        return restTemplate.exchange(url, httpMethod, httpEntity, String.class);
    } catch(HttpStatusCodeException e) {
        return ResponseEntity.status(e.getRawStatusCode()).headers(e.getResponseHeaders())
                .body(e.getResponseBodyAsString());
    }

This will return all the expected results from the response.

Fontana answered 1/8, 2018 at 20:36 Comment(4)
you need to use different HttpClient than default SDK, to get response body for errorsHu
this is helpful, thanks, though I would disagree that assuming things is a clever thing to doSynecdoche
"cleverly" -- citation neededCaresa
Clever aka en.wikipedia.org/wiki/Kludge as opposed to KISS principles-wiki.net/principles:keep_it_simple_stupid. Some developers do not like to use exception handling as a method of process control.Fontana
H
70

You should catch a HttpStatusCodeException exception:

try {
    restTemplate.exchange(...);
} catch (HttpStatusCodeException exception) {
    int statusCode = exception.getStatusCode().value();
    ...
}
Henni answered 29/6, 2016 at 7:52 Comment(12)
IMO the response should always come with an appropriate status code, otherwise whats the purpose of codes.Hodosh
I am not sure to understand @Hodosh objection: catching HttpStatusCodeException is not for a wrong code, but because in many cases an exception is always thrown and so your if(code==value) can never be executed.Clackmannan
Exceptions are very costly in Java. It's OK for the occasional, unexpected causes (hence the name), but over that, you should look for other solutions instead.Szombathely
"Very costly"? More costly than, say, making an HTTP call?Cocainize
How can we get response body from the exception? Is it possible?Cyanosis
@AgostonHorvath - rubbish. In this case you've paid the price of the exception already anyway. The point here is to transform it from an otherwise useless HttpClientException into a more meaningful application specific exception using the returned JSON error object.Obstruent
@RaffaelBecharaRameh - HttpStatusCodeException .getResponseBodyAsString() or HttpStatusCodeException.getResponseBodyAsByteArray().Obstruent
@Dave there is no exception created if you process the error via a handler, like the answer above.Szombathely
@AgostonHorvath - the cost of the exception includes the context setup for exception handling that the JVM does whenever it encounters a try block - this is the cost already paid to which I referred. The cost of generating the stack trace is relatively minor although not completely insignificant. Your assertion that "Exceptions are very costly in Java" within a context where some form of error handling must happen, is false.Obstruent
FWIW I personally use the custom ResponseErrorHandler approach, but not because I am trying to avoid using Java exception handling. My reason is because the response body in such cases is the serialized exception.Obstruent
@Dave The try-catch block is practically free. It's new()'ing and throw'ing the exception that is expensive. See #17966765Szombathely
you need to use different HttpClient than default SDK, to get response body for errors (for example apache commons HttpClient)Hu
J
40

Spring abstracts you from the very very very large list of http status code. That is the idea of the exceptions. Take a look into org.springframework.web.client.RestClientException hierarchy:

You have a bunch of classes to map the most common situations when dealing with http responses. The http codes list is really large, you won't want to write code to handle each situation. But for example, take a look into the HttpClientErrorException sub-hierarchy. You have a single exception to map any 4xx kind of error. If you need to go deep, then you can. But with just catching HttpClientErrorException, you can handle any situation where bad data was provided to the service.

The DefaultResponseErrorHandler is really simple and solid. If the response status code is not from the family of 2xx, it just returns true for the hasError method.

Jari answered 3/5, 2019 at 17:46 Comment(4)
Dude, thanks for the explanation. How did you build this tree with exception hierarchy?Depopulate
Hey man, I used Eclipse. Just press control+shift+T to open the type searcher, and type RestClientException. Double click on RestClientException from the results, Eclipse will open that class for you. Then, put the mouse cursor over the class name (where it says "public class RestClientException...", and press control+T. You will see that hierarchy.Jari
Btw in Intellij it is: click on the class in project tree and Ctrl + Alt + U, or right mouse click -> Build diagramDepopulate
In addition to @chillappreciator's tip, I'd like to mention that diagrams are exclusive to Intellij Idea Ultimate. However, in the Community version, you can still use Ctrl + H to access the Type Hierarchy which provides a similar view as shown in the screenshot in this answer.Yingling
I
39

Another solution is the one described here at the end of this post by "enlian": http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate

try{
     restTemplate.exchange(...)
} catch(HttpStatusCodeException e){
     String errorpayload = e.getResponseBodyAsString();
     //do whatever you want
} catch(RestClientException e){
     //no response payload, tell the user sth else 
}
Ingest answered 24/3, 2018 at 3:9 Comment(1)
you need to use different HttpClient than default SDK, to get response body for errors (for example apache commons HttpClient)Hu
P
9

I have handled this as below:

try {
  response = restTemplate.postForEntity(requestUrl, new HttpEntity<>(requestBody, headers), String.class);
} catch (HttpStatusCodeException ex) {
  response = new ResponseEntity<String>(ex.getResponseBodyAsString(), ex.getResponseHeaders(), ex.getStatusCode());
}
Preconize answered 20/3, 2020 at 7:0 Comment(0)
J
6

A very simple solution can be:

try {
     requestEntity = RequestEntity
     .get(new URI("user String"));
    
    return restTemplate.exchange(requestEntity, String.class);
} catch (RestClientResponseException e) {
        return ResponseEntity.status(e.getRawStatusCode()).body(e.getResponseBodyAsString());
}
Josephus answered 13/7, 2020 at 16:52 Comment(0)
V
4

If you use pooling (http client factory) or load balancing (eureka) mechanism with your RestTemplate, you will not have the luxury of creating a new RestTemplate per class. If you are calling more than one service you cannot use setErrorHandler because if would be globally used for all your requests.

In this case, catching the HttpStatusCodeException seems to be the better option.

The only other option you have is to define multiple RestTemplate instances using the @Qualifier annotation.

Also - but this is my own taste - I like my error handling snuggled tightly to my calls.

Vidovic answered 23/1, 2018 at 9:25 Comment(0)
T
4

To extedend @carcaret answer a bit....

Consider your response errors are returned by json message. For example the API may return 204 as status code error and a json message as error list. In this case you need to define which messages should spring consider as error and how to consume them.

As a sample your API may return some thing like this, if error happens:

 { "errorCode":"TSC100" , "errorMessage":"The foo bar error happend" , "requestTime" : "202112827733" .... } 

To consume above json and throw a custom exception, you can do as below:

First define a class for mapping error ro object

//just to map the json to object
public class ServiceErrorResponse implements Serializable {

    //setter and getters
    private Object errorMessage;
    private String errorCode;
    private String requestTime;
   
}

Now define the error handler:

public class ServiceResponseErrorHandler implements ResponseErrorHandler {

    private List<HttpMessageConverter<?>> messageConverters;

    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        
        return (response.getStatusCode().is4xxClientError() ||
                response.getStatusCode().is5xxServerError());
    }

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        
        @SuppressWarnings({ "unchecked", "rawtypes" })
        HttpMessageConverterExtractor<ServiceErrorResponse> errorMessageExtractor = 
                new HttpMessageConverterExtractor(ServiceErrorResponse.class, messageConverters);
        
        ServiceErrorResponse errorObject = errorMessageExtractor.extractData(response);
        
       throw new ResponseEntityErrorException(
               ResponseEntity.status(response.getRawStatusCode())
                                .headers(response.getHeaders())
                                .body(errorObject)
               );
        
    }

    public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        this.messageConverters = messageConverters;
    }
}

The custom Exception will be:

public class ResponseEntityErrorException extends RuntimeException  {
    
    private ResponseEntity<ServiceErrorResponse> serviceErrorResponseResponse;

    public ResponseEntityErrorException(ResponseEntity<ServiceErrorResponse> serviceErrorResponseResponse) {
        this.serviceErrorResponseResponse = serviceErrorResponseResponse;
    }
    
    public ResponseEntity<ServiceErrorResponse> getServiceErrorResponseResponse() {
        return serviceErrorResponseResponse;
    }
}

To use it:

RestTemplateResponseErrorHandler errorHandler = new 
RestTemplateResponseErrorHandler();
//pass the messageConverters to errror handler and let it convert json to object
        errorHandler.setMessageConverters(restTemplate.getMessageConverters());
        restTemplate.setErrorHandler(errorHandler);
Taco answered 26/4, 2021 at 10:57 Comment(0)
A
2

The code of exchange is below:

public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
            HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException

Exception RestClientException has HttpClientErrorException and HttpStatusCodeException exception.

So in RestTemplete there may occure HttpClientErrorException and HttpStatusCodeException exception. In exception object you can get exact error message using this way: exception.getResponseBodyAsString()

Here is the example code:

public Object callToRestService(HttpMethod httpMethod, String url, Object requestObject, Class<?> responseObject) {

        printLog( "Url : " + url);
        printLog( "callToRestService Request : " + new GsonBuilder().setPrettyPrinting().create().toJson(requestObject));

        try {

            RestTemplate restTemplate = new RestTemplate();
            restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            restTemplate.getMessageConverters().add(new StringHttpMessageConverter());


            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);

            long start = System.currentTimeMillis();

            ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);

            printLog( "callToRestService Status : " + responseEntity.getStatusCodeValue());


            printLog( "callToRestService Body : " + new GsonBuilder().setPrettyPrinting().create().toJson(responseEntity.getBody()));

            long elapsedTime = System.currentTimeMillis() - start;
            printLog( "callToRestService Execution time: " + elapsedTime + " Milliseconds)");

            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                return responseEntity.getBody();
            }

        } catch (HttpClientErrorException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }catch (HttpStatusCodeException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }
        return null;
    }

Here is the code description:

In this method you have to pass request and response class. This method will automatically parse response as requested object.

First of All you have to add message converter.

restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            restTemplate.getMessageConverters().add(new StringHttpMessageConverter());

Then you have to add requestHeader. Here is the code:

HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);

Finally, you have to call exchange method:

ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);

For prety printing i used Gson library. here is the gradle : compile 'com.google.code.gson:gson:2.4'

You can just call the bellow code to get response:

ResponseObject response=new RestExample().callToRestService(HttpMethod.POST,"URL_HERE",new RequestObject(),ResponseObject.class);

Here is the full working code:

import com.google.gson.GsonBuilder;
import org.springframework.http.*;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;


public class RestExample {

    public RestExample() {

    }

    public Object callToRestService(HttpMethod httpMethod, String url, Object requestObject, Class<?> responseObject) {

        printLog( "Url : " + url);
        printLog( "callToRestService Request : " + new GsonBuilder().setPrettyPrinting().create().toJson(requestObject));

        try {

            RestTemplate restTemplate = new RestTemplate();
            restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            restTemplate.getMessageConverters().add(new StringHttpMessageConverter());


            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);

            long start = System.currentTimeMillis();

            ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);

            printLog( "callToRestService Status : " + responseEntity.getStatusCodeValue());


            printLog( "callToRestService Body : " + new GsonBuilder().setPrettyPrinting().create().toJson(responseEntity.getBody()));

            long elapsedTime = System.currentTimeMillis() - start;
            printLog( "callToRestService Execution time: " + elapsedTime + " Milliseconds)");

            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                return responseEntity.getBody();
            }

        } catch (HttpClientErrorException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }catch (HttpStatusCodeException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }
        return null;
    }

    private void printLog(String message){
        System.out.println(message);
    }
}

Thanks :)

Amble answered 12/8, 2018 at 5:24 Comment(1)
'org.springframework.web.client.HttpClientErrorException' is a subclass of 'org.springframework.web.client.HttpStatusCodeException'. You don't need to catch HttpClientErrorException if you are already catching HttpStatusCodeException and doing nothing different in handling the above two exceptions.Wristwatch
J
2

This is how to handle exceptions in Rest Template

        try {
        return restTemplate.exchange("URL", HttpMethod.POST, entity, String.class);
            }
        catch (HttpStatusCodeException e) 
        {
        return ResponseEntity.status(e.getRawStatusCode()).headers(e.getResponseHeaders())
                .body(e.getResponseBodyAsString());
        }
Jameson answered 11/4, 2022 at 15:9 Comment(0)
H
1

I fixed it by overriding the hasError method from DefaultResponseErrorHandler class:

public class BadRequestSafeRestTemplateErrorHandler extends DefaultResponseErrorHandler
{
    @Override
    protected boolean hasError(HttpStatus statusCode)
    {
        if(statusCode == HttpStatus.BAD_REQUEST)
        {
            return false;
        }
        return statusCode.isError();
    }
}

And you need to set this handler for restemplate bean:

@Bean
    protected RestTemplate restTemplate(RestTemplateBuilder builder)
    {
        return builder.errorHandler(new BadRequestSafeRestTemplateErrorHandler()).build();
    }
Herbart answered 10/2, 2021 at 23:18 Comment(0)
O
1

There is also an option to use TestRestTemplate. It is very useful for integration and E2E tests, when you need to validate all status codes manually (for example in negative test-cases).

TestRestTemplate is fault-tolerant. This means that 4xx and 5xx do not result in an exception being thrown and can instead be detected via the response entity and its status code.

Orometer answered 15/9, 2022 at 10:43 Comment(0)
S
0

Here is my POST method with HTTPS which returns a response body for any type of bad responses.

public String postHTTPSRequest(String url,String requestJson)
{
    //SSL Context
    CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    //Initiate REST Template
    RestTemplate restTemplate = new RestTemplate(requestFactory);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    //Send the Request and get the response.
    HttpEntity<String> entity = new HttpEntity<String>(requestJson,headers);
    ResponseEntity<String> response;
    String stringResponse = "";
    try {
        response = restTemplate.postForEntity(url, entity, String.class);
        stringResponse = response.getBody();
    }
    catch (HttpClientErrorException e)
    {
        stringResponse = e.getResponseBodyAsString();
    }
    return stringResponse;
}
Spectroheliograph answered 8/4, 2018 at 5:14 Comment(1)
you need to use different HttpClient than default SDK, to get response body for errors (for example apache commons HttpClient)Hu
P
0

Read about global exception handling in global exception handler add the below method. this will work.

@ExceptionHandler( {HttpClientErrorException.class, HttpStatusCodeException.class, HttpServerErrorException.class})
@ResponseBody
public ResponseEntity<Object> httpClientErrorException(HttpStatusCodeException e) throws IOException {
    BodyBuilder bodyBuilder = ResponseEntity.status(e.getRawStatusCode()).header("X-Backend-Status", String.valueOf(e.getRawStatusCode()));
    if (e.getResponseHeaders().getContentType() != null) {
        bodyBuilder.contentType(e.getResponseHeaders().getContentType());
    }
    return bodyBuilder.body(e.getResponseBodyAsString());
}
Psid answered 30/11, 2021 at 1:36 Comment(0)
B
0

Seems that what you're looking for is implemented in org.springframework.boot.test.web.client.TestRestTemplate in a similar way ->

RestTemplateBuilder builder = new RestTemplateBuilder();
builder.errorHandler(new NotThrowingErrorHandler());

    private static class NotThrowingErrorHandler extends DefaultResponseErrorHandler {
        @Override
        public void handleError(ClientHttpResponse response) {
            // do not throw exception on 4xx/5xx, just to return response entity for manual introspection
     
   }
    }
Bubonocele answered 31/1 at 12:55 Comment(0)
T
-2

Try using @ControllerAdvice. This allows you to handle the exception only once and have all 'custom' handled exceptions in one place.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html

example

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler{

    @ExceptionHandler(MyException.class)
    protected ResponseEntity<Object> handleMyException(){
      MyException exception,
      WebRequest webRequest) {
    return handleExceptionInternal(
        exception,
        exception.getMessage(),
        exception.getResponseHeaders(),
        exception.getStatusCode(),
        webRequest);
}
Tosha answered 1/11, 2021 at 12:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.