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
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
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 Boot RestTemplate Error Handling
- Default error handling
- Error handling using try. catch
- Implementing a custom error handler
- Spring resttemplate handle exception
- 2.1. REST operations
- 2.1.1. Asynchronous operations
- 2.1.2. Asynchronous operations using the Task Parallel Library (TPL)
- 2.2. Configuring the RestTemplate
- 2.2.1. Base address
- 2.2.2. HTTP message converters
- 2.2.3. Error handling
- 2.2.4. Request factory
- 2.2.4.1. Silverlight support
- 2.2.5. Request interceptors
- spring-rest-template-error-handling
- Spring RestTemplate Error Handling
- 1. Overview
- 2. Default Error Handling
- 3. Implementing a ResponseErrorHandler
- 4. Testing our Implementation
- 5. Conclusion
- Error Handling for REST with Spring
- Introduction
- 1. Restful API Error / Exception Design
- 1.1 Default Exception Message
- 2. Spring REST Error Handling
- 2.1. Rest API Error Handling Class.
- 2.2. ExceptionHandler Annotation
- 3. The @ControllerAdvice Annotation
- 3.1. ResponseEntityExceptionHandler
- 3.2. MethodArgumentTypeMismatchException
- 3.3. HttpMessageNotReadable
- 4. Handling Custom Exceptions
- 5. Default Exception Handler
- 6. Spring Boot REST Error Handling
- 7. JSR 303 Validation Error (REST API)
- Summary
Spring Boot RestTemplate Error Handling
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:
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:
You can now create an instance of MyErrorHandler and pass it to the RestTemplate 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.
Источник
Spring resttemplate handle exception
Invoking RESTful services in .NET is typically done using the HttpWebRequest class. For common REST operations this approach is too low level as shown below.
There is also another class in the .NET framework, WebClient , that might also be considered for this long-hand approach but its lack of support for HTTP headers and HTTP status codes/descriptions generally makes it unsuitable for use in these kinds of broader HTTP interactions.
RestTemplate provides higher level methods that correspond to each of the six main HTTP methods that make invoking many RESTful services a one-liner and enforce REST best practices.
2.1. REST operations
Note | |
---|---|
HTTP Method | RestTemplate Method |
DELETE | Delete(Uri url) and 2 more |
GET | GetForObject (Uri url) and 2 more |
GetForMessage (Uri url) and 2 more | |
HEAD | HeadForHeaders(Uri url) and 2 more |
OPTIONS | OptionsForAllow(Uri url) and 2 more |
POST | PostForLocation (Uri url, object request) and 2 more |
PostForObject (Uri url, object request) and 2 more | |
PostForMessage (Uri url, object request) and 2 more | |
PostForMessage(Uri url, object request) and 2 more | |
PUT | Put(Uri url, object request) and 2 more |
The two other methods mentioned take URI template arguments in two forms, either as a object variable length argument or an IDictionary .
For example,
The names of RestTemplate methods follow a naming convention, the first part indicates what HTTP method is being invoked and the second part indicates what is returned. For example,
The method GetForObject () will perform a GET, and return the HTTP response body converted into an object type of your choice.
The method PostForLocation() will do a POST, converting the given object into a HTTP request and return the response HTTP Location header where the newly created object can be found.
The method PostForMessage () will do a POST, converting the given object into a HTTP request and return the full HTTP response message composed of the status code and description, the response headers and the response body converted into an object type of your choice.
The request object to be POSTed or PUTed, may be a HttpEntity instance in order to add additional HTTP headers. An example is shown below.
These operations are synchronous and thus are not available for Silverlight and Windows Phone because all related network calls have to be asynchronous.
2.1.1. Asynchronous operations
All REST operations may also be invoked asynchronously as well.
The asynchronous methods are suffixed by the word ‘Async’ based on common .NET naming conventions.
Although asynchronous call were designed to support Silverlight and Windows Phone, asynchronous invocation can also be used for Windows Forms and WPF applications, etc. to avoid blocking the UI when invoking RESTful services.
An example using an asynchronous method is shown below :
2.1.2. Asynchronous operations using the Task Parallel Library (TPL)
All REST operations supports also the new Task-based Asynchronous Pattern (TAP) for asynchronous calls.
This is based on the new Task Parallel Library (TPL) provided with .NET 4 and Silverlight 5.
These asynchronous methods are suffixed by the word ‘Async’ based on common .NET naming conventions, and each returns a Task .
An example is shown below :
Note | |
---|---|
Do not forget to synchronize with the UI thread to interact with the user interface in a continuation as shown below :
Warning | |
---|---|
Using the new TPL is the recommended way to perform asynchronous operations in .NET 4.0 and later.
2.2. Configuring the RestTemplate
2.2.1. Base address
In some cases it may be useful to set up the base url of the request once. This is possible by setting the base address in the constructor or by setting the property BaseAddress .
For example:
2.2.2. HTTP message converters
Objects passed to and returned from REST operations are converted to and from HTTP messages by IHttpMessageConverter instances.
Converters for the main mime types are registered by default, but you can also write your own converter and register it via the MessageConverters property.
The default converter instances registered with the template, depending of the target Framework, are ByteArrayHttpMessageConverter , StringHttpMessageConverter , FormHttpMessageConverter , XmlDocumentHttpMessageConverter , XElementHttpMessageConverter , Atom10FeedHttpMessageConverter and Rss20FeedHttpMessageConverter .
You can override these defaults using the MessageConverters property. This is required if you want to use the XmlSerializableHttpMessageConverter / DataContractHttpMessageConverter or DataContractJsonHttpMessageConverter / NJsonHttpMessageConverter .
See HTTP message conversion chapter for detailed description of each converter.
2.2.3. Error handling
In case of an exception processing the HTTP method, an exception of the type RestClientException will be thrown. The interface IResponseErrorHandler allows you to determine whether a particular response has an error and to handle it. The default implementation DefaultResponseErrorHandler throws an exception when a HTTP client or server error happens (HTTP status code 4xx or 5xx).
The default behavior can be changed by plugging in another implementation into the RestTemplate via the ErrorHandler property. The example below shows a custom IResponseErrorHandler implementation that inherits from existing DefaultResponseErrorHandler and which throws an exception only when a HTTP server error happens (HTTP status code 5xx).
Note | |
---|---|
ErrorHandler property may be set to null to give the user total control over the response.
2.2.4. Request factory
RestTemplate uses a request factory to create instances of the IClientHttpRequest interface. Default implementation uses the .NET Framework class HttpWebRequest . This can be overridden by specifying an implementation of IClientHttpRequestFactory via the RequestFactory property.
The default implementation WebClientHttpRequestFactory uses an instance of HttpWebRequest which can be configured with credentials information or proxy settings. An example setting the proxy is shown below :
2.2.4.1. Silverlight support
In Silverlight, HTTP handling can be performed by the browser or the client. See How to: Specify Browser or Client HTTP Handling on MSDN.
By default, WebClientHttpRequestFactory will use the browser HTTP stack for HTTP methods GET and POST, and force the client HTTP stack for other HTTP methods.
This can be overridden by setting the WebRequestCreator property.
2.2.5. Request interceptors
RestTemplate allows you to intercept HTTP request creation and/or execution. You can create your own interceptor and register it via the RequestInterceptors property.
Four types of interceptors are provided :
IClientHttpRequestFactoryInterceptor will intercept request creation, allowing to modify the HTTP URI and method, and to customize the newly created request.
IClientHttpRequestBeforeInterceptor will intercept request before its execution, allowing to modify the HTTP headers and body. This interceptor supports both synchronous and asynchronous requests.
IClientHttpRequestSyncInterceptor will intercept synchronous request execution, giving total control of the execution (error management, logging, perf, etc.).
IClientHttpRequestAsyncInterceptor will intercept asynchronous request execution, giving total control of the execution (error management, logging, perf, etc.).
An example of an interceptor measuring HTTP request execution time is shown below.
Note that you can also make PerfRequestSyncInterceptor implement IClientHttpRequestAsyncInterceptor to support both synchronous and asynchronous requests.
Источник
spring-rest-template-error-handling
Spring RestTemplate Error Handling
1. Overview
In this short tutorial, we’ll discuss how to implement and inject the ResponseErrorHandler interface in a RestTemplate instance – to gracefully handle HTTP errors returned by remote APIs.
2. Default Error Handling
By default, the RestTemplate will throw one of these exceptions in case of an HTTP error:
HttpClientErrorException – in case of HTTP status 4xx
HttpServerErrorException – in case of HTTP status 5xx
UnknownHttpStatusCodeException – in case of an unknown HTTP status
All these exceptions are extensions of RestClientResponseException.
Obviously, the simplest strategy to add a custom error handling is to wrap the call in a try/catch block. Then, we process the caught exception as we see fit.
However, this simple strategy doesn’t scale well as the number of remote APIs or calls increases. It’d be more efficient if we could implement a reusable error handler for all of our remote calls.
3. Implementing a ResponseErrorHandler
And so, a class that implements ResponseErrorHandler will read the HTTP status from the response and either:
Throw an exception that is meaningful to our application
Simply ignore the HTTP status and let the response flow continue without interruption
We need to inject the ResponseErrorHandler implementation into the RestTemplate instance.
Hence, we use the RestTemplateBuilder to build the template and replace the DefaultResponseErrorHandler in the response flow.
So let’s first implement our RestTemplateResponseErrorHandler:
Next, we build the RestTemplate instance using the RestTemplateBuilder to introduce our RestTemplateResponseErrorHandler:
4. Testing our Implementation
Finally, let’s test this handler by mocking a server and returning a NOT_FOUND status:
5. Conclusion
This article presented a solution to implement and test a custom error handler for a RestTemplate that converts HTTP errors into meaningful exceptions.
As always, the code presented in this article is available over on Github.
Источник
Error Handling for REST with Spring
In the earlier post of REST with Spring series, we discussed Spring MVC Content Negotiation.In this article, we will discuss how to implement Spring REST Exception Handling.
Introduction
Exception handling in a RESTful API with a meaningful error message and the status code is a desired and must have feature. A good error message helps API client to take corrective actions. Some application sends the exception stack trace which can be a good option for a typical web application however this is not a very good solution for a REST API. In this post, we will discuss and implement Error Handling with Spring for a REST API.
In this article, we are using Spring Boot to develop and work on our REST API. Spring Boot provides several features which makes it easy and flexible to create REST API’s with minimum configurations. We will look at few methods outlining how to do a better error handling for REST API using Spring Boot.
1. Restful API Error / Exception Design
While designing exception handling in the RESTful API, it’s a good practice to set HTTP status code in the response to communicate why the request failed or showing a success. We should send more information along with HTTP status code. It will help the client understand the error and take any corrective action.
Let’s discuss this response to understand important points while designing response for your REST API.
- The status represents HTTP status code.
- error_code represents REST API specific error code.This field is helpful to pass on API / domain specific information.
- The message field represents human-readable error message.
- The details section represents error information with complete detail.
- The information_link field specifies a link for detail information about the error or exception.
I am dividing this article in to 2 section. The first half talks about the importance of a better error handling for the REST API and the second half focused on the Spring Boot built-in feature for REST Exception Handling. We are taking a simple example of an online ecommerce application where our client can create a customer or get customer information by passing the customer id in the request.
1.1 Default Exception Message
Let’s see how the response look like in case REST API do not have a clear response.
This is the default output when there is no custom error response for our REST API. Though it provides a lot of information but it’s difficult for the client API to parse every exception and display the error message to the customer. There should be a better way to communicate these exceptions to the client to show a better error message.
2. Spring REST Error Handling
Spring and Spring Boot provides several options for error/exception handling. Let’s see what are the different options for Error Handling in REST API with Spring.Before we move in to the details, let’s create an Error response class for our Rest exception handling example.
2.1. Rest API Error Handling Class.
We will use this class to rest customer error message from our REST Api.
2.2. ExceptionHandler Annotation
The first approach is to use the ExceptionHandler annotation at the controller level. This annotation specifically handle exceptions thrown by request handling ( @RequestMapping ) methods in the same controller. Let’s take an example where service can throw CustomerNotFound Exception but we like to send a different / customize message to the client API.
We can define as many @RequestMapping in our controller (Having a different mapping for a different exception type).There are multiple problems or drawbacks with the approach.
- This annotation is only active for the controller.
- This annotation is not global and we need to add to every controller (not very intuitive).
- The return type for this is void or String which add a lot of constraints.
Most of the enterprise application work by extending a basic controller (having common controller functionalities). We can overcome @ExceptionHandler limitation by adding it to the base controller. This also has multiple limitations.
- The base controller is not suitable for all type of controller. We will end up by duplicating out code.
- Our Controllers may have to extend a third party class not under our control.
[pullquote align=”normal”]With all these limitations, I do not recommend it to use this approach while building your RESTful API [/pullquote]
3. The @ControllerAdvice Annotation
Spring 3.2 introduced @ControllerAdvice annotation which supports global Exception handler mechanism. A controller advice allows you to use exactly the same exception handling techniques but applies them across the application, not just to an individual controller.
If we call our customer API endpoint again with an invalid user id, we will get the following response from the API
Let’s discuss some important points for the @ControllerAdvice annotation:
- The @ControllerAdvice annotation is a specialized @Component annotation. We have the flexibility to use this annotation for multiple controller classes (This works based on the Exception and not bind to the Controller).
- Spring Boot automatically detects all classes annotated with @ControllerAdvice during startup as part of the classpath scanning.
- We can narrow down the selected controllers by using basePackageClasses() , and basePackages() parameters. For more details refer to the ControllerAdvice.
3.1. ResponseEntityExceptionHandler
In the above example we extended ResponseEntityExceptionHandler class. This is a convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods. This is a convenient way while working on Spring based REST API since it allows the developer to specify ResponseEntity as return values. Let’s work on some most common client errors. We will look into few scenarios of a client sending an invalid request.
3.2. MethodArgumentTypeMismatchException
It throws this exception when method arguments are not the expected type
3.3. HttpMessageNotReadable
It throws this exception when API cannot read the HTTP message
Below we can see the answer to a REST call
4. Handling Custom Exceptions
While working on REST API, we may come across multiple use cases when a request is not fulfilled and we want to return a custom exception back to the client API. Let’s take a simple use case when client API call to find a customer by its unique id. Our service call might return a null or empty object if we do not find the object. Here, if not handled correctly, the API will return 200 (OK) response to the client even if no record found. Let’s create a simple example for better clarity:
Here is the sample service file.
In above example, if we send a request with the user id as 1, our REST API send 200 (OK) response.
To handle all similar use cases, we create a custom exception and handle this exception in our GlobalRestExceptionHandler
I will not go into details about handling different Exceptions in the REST API since we can handle all Exceptions in a similar way as explained above. Here is the list of some common exceptions in a REST API.
- HttpMediaTypeNotSupportedException
- HttpRequestMethodNotSupportedException
- TypeMismatchException
5. Default Exception Handler
We can not handle each exception within the system. Let’s create a fallback handler which will handle all exceptions that don’t have specific exception handler.
6. Spring Boot REST Error Handling
Spring Boot provides several features to build RESTful API’s. Spring Boot 1.4 introduced the @RestControllerAdvice annotation for easier exception handling. It is a convenience annotation that is itself annotated with @ControllerAdvice and @ResponseBody. Here is an example.
While using above approach, set following property to true in Spring Boot application.properties file
7. JSR 303 Validation Error (REST API)
The JSR 303 or also known as bean validation is a standard way to validate your incoming data. The @valid annotation throws handleMethodArgumentNotValid error if the incoming data is not valid. In case we like to provide a custom error message, we have the same option to add a separate handler in our GlobalRestExceptionHandler class. This is our simple CustomerController :
We have added bean validation constraints to our Customer class:
Here is the custom spring rest exception handling method in our GlobalRestExceptionHandler class.
When we call our REST controller, we will have a custom error response based on the JSR 303 bean validation.
Summary
It is important to handle and process exceptions properly in the Spring bases REST API. In this post, we covered different options to implement Spring REST Exception Handling. Building a good exception handling workflow for REST API is an iterative and complex process. A good exception handling mechanism allows API client to know what went wrong with the request. The source code is available on the GitHub.
Источник
Adblock
detector
Note | |
---|---|