Resttemplate обработка ошибок

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 :)

In an earlier article, I wrote about making HTTP requests using the RestTemplate class in a Spring Boot application.

In this short article, you’ll learn how to handle the errors thrown by the RestTemplate, during the execution of an HTTP request.

Default error handling

By default, if there is an error during the execution of the request or the server returns a non-successful HTTP status code (4xx or 5xx), RestTemplate will throw one of the following exceptions:

  • HttpClientErrorException — For HTTP status code 4xx
  • HttpServerErrorException — For HTTP status code 5xx
  • UnknownHttpStatusCodeException — In case of an unknown HTTP status code

All these exceptions extend a base class called RestClientResponseException that contains actual HTTP response data.

Error handling using try...catch

The simplest way to add a custom error handler is to use a try-catch block to catch the HttpStatusCodeException exception. From the HttpStatusCodeException instance, you can then get the response status code, body, and headers, as shown below:

try {
    // request url
    String url = "https://reqres.in/api/unknown/23";

    // create an instance of RestTemplate
    RestTemplate restTemplate = new RestTemplate();

    // make an HTTP GET request
    ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
    
} catch (HttpStatusCodeException ex) {
    // raw http status code e.g `404`
    System.out.println(ex.getRawStatusCode());
    
    // http status code e.g. `404 NOT_FOUND`
    System.out.println(ex.getStatusCode().toString());
    
    // get response body
    System.out.println(ex.getResponseBodyAsString());
    
    // get http headers
    HttpHeaders headers = ex.getResponseHeaders();
    System.out.println(headers.get("Content-Type"));
    System.out.println(headers.get("Server"));
}

Implementing a custom error handler

Sometimes, a try-catch block is not enough to handle errors as it is not scalable when the number of HTTP requests increases.

You may want to create a reusable custom error handler by implementing the ResponseErrorHandler interface as follows:

MyErrorHandler.java

package com.attacomsian.runner;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.ResponseErrorHandler;

import java.io.IOException;

public class MyErrorHandler implements ResponseErrorHandler {

    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        return new DefaultResponseErrorHandler().hasError(response);
    }

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {

        if (response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) {
            // handle 5xx errors
            // raw http status code e.g `500`
            System.out.println(response.getRawStatusCode());

            // http status code e.g. `500 INTERNAL_SERVER_ERROR`
            System.out.println(response.getStatusCode());

        } else if (response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {
            // handle 4xx errors
            // raw http status code e.g `404`
            System.out.println(response.getRawStatusCode());

            // http status code e.g. `404 NOT_FOUND`
            System.out.println(response.getStatusCode());

            // get response body
            System.out.println(response.getBody());

            // get http headers
            HttpHeaders headers = response.getHeaders();
            System.out.println(headers.get("Content-Type"));
            System.out.println(headers.get("Server"));
        }
    }
}

You can now create an instance of MyErrorHandler and pass it to the RestTemplate class:

// request url
String url = "https://reqres.in/api/unknown/23";

// create an instance of RestTemplate
RestTemplate restTemplate = new RestTemplate();

// set custom error handler
restTemplate.setErrorHandler(new MyErrorHandler());

// make an HTTP GET request
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);

Check out the Making HTTP Requests using RestTemplate in Spring Boot guide for more RestTemplate examples.

✌️ Like this article? Follow me on
Twitter
and LinkedIn.
You can also subscribe to
RSS Feed.

Handling exceptions coming from your RestTemplate instances is important because they are subclasses of RuntimeException so if you don’t catch them they will be thrown up to your top layer (let’s say a @RestController layer).

To handle those exceptions you can catch them in a @ControllerAdvice error handler.

Here is a way to handle RestTemplate exceptions, in this example the application have multiple RestTemplate instances that calls different APIs.

List the APIs you call in an enum

First let’s define an enum that will list all the downstream APIs you will call using RestTemplate.

This enumeration will be used for logging purposes, so that when you catch an exception you can log (or do whatever you want) that an error happened when calling that downstream API.

public enum DownstreamApi {
  MY_API_1,
  MY_API_2
  // TODO list all the downstream APIs here
}

Custom RestTemplate error handling

When using a RestTemplate, the default error handling will throw an exception when the call returned a HTTP 4xx or HTTP 5xx.

The goal here is to convert each HTTP 4xx and HTTP 5xx to a custom RuntimeException.

This custom exception will hold information about the downstream API, the HTTP response status and an error message.

public class MyRestTemplateException extends RuntimeException {

  private DownstreamApi api;
  private HttpStatus statusCode;
  private String error;

  public MyRestTemplateException(DownstreamApi api, HttpStatus statusCode, String error) {
    super(error);
    this.api = api;
    this.statusCode = statusCode;
    this.error = error;
  }
  
  // TODO getters ... 
  // TODO toString ...
}

Here is the code that will catch every HTTP 4xx and 5xx errors thrown by RestTemplate when calling the API “MY_API1”.

You should create a custom error handler per API so you can handle them in a different way (for example if they have different error responses you can parse it here and provide more information in your “global” exception MyRestTemplateException.java).

public class MyApiRestTemplateErrorHandler extends DefaultResponseErrorHandler {

  @Override
  public void handleError(ClientHttpResponse response) throws IOException {
    if (response.getStatusCode().is4xxClientError() || response.getStatusCode().is5xxServerError()) {
      try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.getBody()))) {
        String httpBodyResponse = reader.lines().collect(Collectors.joining(""));

        // TODO deserialize (could be JSON, XML, whatever...) httpBodyResponse to a POJO that matches the error structure for that specific API, then extract the error message.
        // Here the whole response will be treated as the error message, you probably don't want that.
        String errorMessage = httpBodyResponse;

        throw new MyRestTemplateException(DownstreamApi.MY_API_1, response.getStatusCode(), errorMessage);
      }
    }
  }
}

DAO

The DAO will make the REST call by using RestTemplate. The RestTemplate instance is created in a way it is using the custom RestTemplate Error Handler we defined earlier.

@Component
public class MyDao {

  // This is the RestTemplate for DownstreamApi.MY_API_1
  private RestTemplate restTemplateApi1;

  public MyDao(RestTemplateBuilder restTemplateBuilder) {
    this.restTemplateApi1 = restTemplateBuilder
        .errorHandler(new MyApiRestTemplateErrorHandler())
        .build();
  }
  
  public void updateStuff(String param1) {
    URI uri = UriComponentsBuilder
        .fromUriString("https://downstream-api.com/stuff/{param}")
        .build(param1);

    RequestEntity<Void> requestEntity = RequestEntity
        .post(uri)
        .build();

    restTemplateApi1.exchange(requestEntity, Object.class);
  }
  
  // TODO other API calls
  
}

Controller advice

Once you have converted the RestTemplate exceptions to MyRestTemplateException, you have to catch them in a global Exception Handler and convert it to a “generic” error response (here ErrorResponse.java)

Here is the “generic” error response POJO:

public class ErrorResponse {

  private String timestamp;

  /** HTTP Status Code */
  private int status;

  /** HTTP Reason phrase */
  private String error;

  /** A message that describe the error thrown when calling the downstream API */
  private String message;

  /** Downstream API name that has been called by this application */
  private DownstreamApi api;

  /** URI that has been called */
  private String path;

  public ErrorResponse(MyRestTemplateException ex, String path) {
    this.timestamp = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
    this.status = ex.getStatusCode().value();
    this.error = ex.getStatusCode().getReasonPhrase();
    this.message = ex.getError();
    this.api = ex.getApi();
    this.path = path;
  }

  // TODO getters ... 
  // TODO toString ...
}

Then defined the error handler in a @ControllerAdvice class and you are good to go!

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

  private static final Logger LOGGER = LoggerFactory.getLogger(MyExceptionHandler.class);

  @ExceptionHandler(value = MyRestTemplateException.class)
  ResponseEntity<ErrorResponse> handleMyRestTemplateException(MyRestTemplateException ex, HttpServletRequest request) {
    LOGGER.error("An error happened while calling {} Downstream API: {}", ex.getApi(), ex.toString());
    return new ResponseEntity<>(new ErrorResponse(ex, request.getRequestURI()), ex.getStatusCode());
  }
}

Controller layer

@RestController
public class MyController {

  private final MyDao dao;

  MyController(MyDao dao){
    this.dao = dao;
  }

  @PostMapping("/update-stuff")
  public void updateStuff() {
    return dao.updateStuff("blabla");
  }

Sample of error returned

Here is the error response that would be returned when calling POST /update-stuff if the downstream API “MY_API1” throws an error:

{
  "timestamp": "2019-06-07T19:51:30.907",
  "status": 404,
  "error": "Not Found",
  "message": "The stuff 'blabla' has not been found",
  "api": "MY_API_1",
  "path": "/update-stuff"
}

Tested with Spring Boot 2.1.5.RELEASE

Spring RestTemplate Обработка ошибок

1. обзор

В этом коротком руководстве мы обсудим, как реализовать и внедрить интерфейсResponseErrorHandler в экземплярRestTemplate — для корректной обработки ошибок HTTP, возвращаемых удаленными API. 

2. Обработка ошибок по умолчанию

По умолчаниюRestTemplate выдает одно из этих исключений в случае ошибки HTTP:

  1. HttpClientErrorException — в случае HTTP-статуса 4xx

  2. HttpServerErrorException – in случай статуса HTTP 5xx

  3. UnknownHttpStatusCodeException – in случай неизвестного статуса HTTP

Все эти исключения являются расширениямиRestClientResponseException.

Obviously, the simplest strategy to add a custom error handling is to wrap the call in a try/catch block. Затем мы обрабатываем пойманное исключение по своему усмотрению.

Однакоthis simple strategy doesn’t scale well по мере увеличения количества удаленных API или вызовов. Было бы более эффективно, если бы мы могли реализовать многоразовый обработчик ошибок для всех наших удаленных вызовов.

3. РеализацияResponseErrorHandler

Итак, класс, реализующийResponseErrorHandler will, считывает статус HTTP из ответа и либо:

  1. Добавьте исключение, которое имеет значение для нашего приложения

  2. Просто игнорируйте статус HTTP и позвольте потоку ответа продолжаться без прерывания

Нам нужно внедрить реализациюResponseErrorHandler в экземплярRestTemplate.

Следовательно, мы используемRestTemplateBuilder для создания шаблона и заменяемDefaultResponseErrorHandler в потоке ответов.

Итак, давайте сначала реализуем нашRestTemplateResponseErrorHandler:

@Component
public class RestTemplateResponseErrorHandler
  implements ResponseErrorHandler {

    @Override
    public boolean hasError(ClientHttpResponse httpResponse)
      throws IOException {

        return (
          httpResponse.getStatusCode().series() == CLIENT_ERROR
          || httpResponse.getStatusCode().series() == SERVER_ERROR);
    }

    @Override
    public void handleError(ClientHttpResponse httpResponse)
      throws IOException {

        if (httpResponse.getStatusCode()
          .series() == HttpStatus.Series.SERVER_ERROR) {
            // handle SERVER_ERROR
        } else if (httpResponse.getStatusCode()
          .series() == HttpStatus.Series.CLIENT_ERROR) {
            // handle CLIENT_ERROR
            if (httpResponse.getStatusCode() == HttpStatus.NOT_FOUND) {
                throw new NotFoundException();
            }
        }
    }
}

Затем мы создаем экземплярRestTemplate, используяRestTemplateBuilder to , вводим нашRestTemplateResponseErrorHandler:

@Service
public class BarConsumerService {

    private RestTemplate restTemplate;

    @Autowired
    public BarConsumerService(RestTemplateBuilder restTemplateBuilder) {
        RestTemplate restTemplate = restTemplateBuilder
          .errorHandler(new RestTemplateResponseErrorHandler())
          .build();
    }

    public Bar fetchBarById(String barId) {
        return restTemplate.getForObject("/bars/4242", Bar.class);
    }

}

4. Тестирование нашей реализации

Наконец, давайте протестируем этот обработчик, имитируя сервер и возвращая статусNOT_FOUND:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = { NotFoundException.class, Bar.class })
@RestClientTest
public class RestTemplateResponseErrorHandlerIntegrationTest {

    @Autowired
    private MockRestServiceServer server;

    @Autowired
    private RestTemplateBuilder builder;

    @Test(expected = NotFoundException.class)
    public void  givenRemoteApiCall_when404Error_thenThrowNotFound() {
        Assert.assertNotNull(this.builder);
        Assert.assertNotNull(this.server);

        RestTemplate restTemplate = this.builder
          .errorHandler(new RestTemplateResponseErrorHandler())
          .build();

        this.server
          .expect(ExpectedCount.once(), requestTo("/bars/4242"))
          .andExpect(method(HttpMethod.GET))
          .andRespond(withStatus(HttpStatus.NOT_FOUND));

        Bar response = restTemplate
          .getForObject("/bars/4242", Bar.class);
        this.server.verify();
    }
}

5. Заключение

В этой статье представлено решение для реализации и тестирования пользовательского обработчика ошибок дляRestTemplate, который преобразует ошибки HTTP в значимые исключения.

Как всегда, доступен код, представленный в этой статьеover on Github.

RestTemplate throws RestClientResponseException subtypes such as HttpClientErrorException, HttpServerErrorException and UnknownHttpStatusCodeException separately if the response HTTP status code is 4xx, 5xx and unknown

You can handle RestTemplate errors at the local level by catching the RestClientResponseException, at the bean level by implementing the ResponseErrorHandler interface and plugging into a RestTemplate bean

Let’s walk through this tutorial to explore them in more detail examples

The RestClientResponseException

RestClientResponseException is a common base class for exceptions that contain actual HTTP response data

You can use getRawStatusCode, getStatusText, getResponseHeaders, getResponseBodyAsString to get HTTP status code in integer number, get HTTP response headers, and get HTTP response body as a String. They are useful when you need to log and return detail error to the client

Catch RestClientResponseException

RestTemplate can only return a ResponseEntity with HTTP 2xx status. With 4xx and 5xx status, RestTemplate throws HttpClientErrorException and HttpServerErrorException which are extended from HttpStatusCodeException and RestClientResponseException hierarchically

<T> ResponseEntity consumeWebService(String url, Class<T> responseType) {  
    try {
        return restTemplate.getForEntity(url, responseType);
    } catch (RestClientResponseException e) {
        return ResponseEntity
            .status(e.getRawStatusCode())
            .body(e.getResponseBodyAsString());
    }
}

Beside catching RestClientResponseException, you can also catch HttpStatusCodeException

The following gives you an integration test example with MockRestServiceServer


Implement ResponseErrorHandler

If you don’t like try-catch every time executing a RestTemplate request, try to implement ResponseErrorHandler and add it to the RestTemplate bean


hasError method is used to indicate whether the given response has any errors

The two handleError methods are used to handle the error in the given response. Only handleError(response) is required to implement, however, it will be overshadowed if you implement handleError(URI url, HttpMethod method, ClientHttpResponse response)

Plug your custom ResponseErrorHandler into a RestTemplate bean

To make your custom ResponseErrorHandler work, you have to register it with a RestTemplate bean

In practice, to follow DRY and leverage the Spring Dependency Injection, you can define your RestTemplate beans in a separate class config and inject it via @Autowired into where it is used such as Spring @Component or @Service class



The following gives you an integration test example with SpringRunner and MockRestServiceServer


Conclusion

In this tutorial, we learned to handle RestTemplate error by catching RestClientResponseException and implementing ResponseErrorHandler. You can find the full source code on GitHub

Share to social

Giau Ngo

Giau Ngo is a software engineer, creator of HelloKoding. He loves coding, blogging, and traveling. You may find him on GitHub and LinkedIn

Понравилась статья? Поделить с друзьями:
  • Return to castle wolfenstein ошибка opengl как исправить
  • Restricted card ошибка
  • Restraint system faulty bmw e60 ошибка
  • Ricoh 4510 sc332 00 ошибка
  • Ricoh 430 ошибка sc400