Error Uploading Sourcemaps to Sentry: Request Failed Because Api Url Was Incorrectly Formatted
Treatment errors correctly in APIs while providing meaningful mistake messages is a very desirable feature, as information technology can help the API customer properly answer to problems. The default behavior tends to be returning stack traces that are hard to sympathize and ultimately useless for the API client. Partitioning the error information into fields also enables the API client to parse information technology and provide better error letters to the user. In this article, we will encompass how to do proper error treatment when building a Residual API with Bound Kick.
Building Residue APIs with Spring became the standard approach for Java developers during the last couple of years. Using Spring Boot helps essentially, as it removes a lot of boilerplate lawmaking and enables motorcar-configuration of various components. We will assume that you're familiar with the basics of API development with those technologies before applying the knowledge described here. If you are still unsure almost how to develop a basic Residue API, then you should start with this article almost Jump MVC or some other one well-nigh building a Spring Residue Service.
Making Mistake Responses Clearer
Throughout this commodity, we'll be using the source lawmaking hosted on GitHub of an application that implements a REST API for retrieving objects that represent birds. Information technology has the features described in this article and a few more examples of error handling scenarios. Here'due south a summary of endpoints implemented in that application:
</tr>
Go /birds/{birdId} | Gets information about a bird and throws an exception if non constitute. |
GET /birds/noexception/{birdId} | This call too gets information almost a bird, except information technology doesn't throw an exception in example that the bird is not found. |
POST /birds | Creates a bird. |
The Spring framework MVC module comes with some great features to help with error handling. But it is left to the programmer to use those features to treat the exceptions and return meaningful responses to the API client.
Let's wait at an case of the default Bound Kick answer when we issue an HTTP POST to the /birds
endpoint with the following JSON object, that has the string "aaa" on the field "mass," which should be expecting an integer:
{ "scientificName": "Common blackbird", "specie": "Turdus merula", "mass": "aaa", "length": iv }
The Leap Kick default answer, without proper error treatment:
{ "timestamp": 1500597044204, "condition": 400, "mistake": "Bad Request", "exception": "org.springframework.http.converter.HttpMessageNotReadableException", "bulletin": "JSON parse mistake: Unrecognized token '3': was expecting ('true', 'fake' or 'nil'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'aaa': was expecting ('true', 'imitation' or 'null')\n at [Source: java.io.PushbackInputStream@cba7ebc; line: 4, column: 17]", "path": "/birds" }
Well… the response message has some good fields, only it is focused too much on what the exception was. By the way, this is the grade DefaultErrorAttributes
from Spring Boot. The timestamp
field is an integer number that doesn't even carry information of what measurement unit of measurement the timestamp is in. The exception
field is only interesting to Coffee developers and the message leaves the API consumer lost in all the implementation details that are irrelevant to them. And what if there were more details that we could extract from the exception that the error originated from? So let's learn how to treat those exceptions properly and wrap them into a nicer JSON representation to make life easier for our API clients.
Equally nosotros'll be using Java 8 appointment and time classes, we first need to add a Maven dependency for the Jackson JSR310 converters. They have intendance of converting Coffee 8 date and time classes to JSON representation using the @JsonFormat
annotation:
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency>
Ok, so let'due south define a class for representing API errors. Nosotros'll be creating a class called ApiError
that has enough fields to concord relevant information about errors that happen during Residual calls.
grade ApiError { individual HttpStatus condition; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") individual LocalDateTime timestamp; individual String message; private Cord debugMessage; private List<ApiSubError> subErrors; private ApiError() { timestamp = LocalDateTime.now(); } ApiError(HttpStatus status) { this(); this.status = status; } ApiError(HttpStatus status, Throwable ex) { this(); this.status = status; this.message = "Unexpected mistake"; this.debugMessage = ex.getLocalizedMessage(); } ApiError(HttpStatus status, String message, Throwable ex) { this(); this.status = status; this.message = message; this.debugMessage = ex.getLocalizedMessage(); } }
-
The
status
property holds the operation telephone call condition. It volition exist annihilation from 4xx to signalize client errors or 5xx to mean server errors. A mutual scenario is a http code 400 that means a BAD_REQUEST, when the customer, for example, sends an improperly formatted field, like an invalid electronic mail address. -
The
timestamp
property holds the date-time instance of when the mistake happened. -
The
message
holding holds a user-friendly message well-nigh the mistake. -
The
debugMessage
holding holds a system message describing the error in more than detail. -
The
subErrors
property holds an assortment of sub-errors that happened. This is used for representing multiple errors in a unmarried call. An instance would be validation errors in which multiple fields have failed the validation. TheApiSubError
grade is used to encapsulate those.
abstract grade ApiSubError { } @Data @EqualsAndHashCode(callSuper = simulated) @AllArgsConstructor grade ApiValidationError extends ApiSubError { private String object; individual String field; private Object rejectedValue; private String bulletin; ApiValidationError(Cord object, String message) { this.object = object; this.bulletin = bulletin; } }
And so and then the ApiValidationError
is a course that extends ApiSubError
and expresses validation problems encountered during the Residuum call.
Beneath, you'll see some examples of JSON responses that are existence generated afterwards we have implemented the improvements described here, just to become an thought of what we'll have past the end of this commodity.
Here is an example of JSON returned when an entity is not plant while calling endpoint GET /birds/2
:
{ "apierror": { "status": "NOT_FOUND", "timestamp": "18-07-2017 06:twenty:19", "message": "Bird was not constitute for parameters {id=2}" } }
Here is another example of JSON returned when issuing a POST /birds
call with an invalid value for the bird'due south mass:
{ "apierror": { "status": "BAD_REQUEST", "timestamp": "18-07-2017 06:49:25", "message": "Validation errors", "subErrors": [ { "object": "bird", "field": "mass", "rejectedValue": 999999, "bulletin": "must exist less or equal to 104000" } ] } }
Bound Boot Error Handling
Let's explore some of the Bound annotations that volition be used to handle exceptions.
RestController
is the base annotation for classes that handle Residuum operations.
ExceptionHandler
is a Leap annotation that provides a mechanism to treat exceptions that are thrown during execution of handlers (Controller operations). This annotation, if used on methods of controller classes, will serve as the entry point for handling exceptions thrown within this controller simply. Altogether, the nigh common way is to employ @ExceptionHandler
on methods of @ControllerAdvice
classes so that the exception handling will exist applied globally or to a subset of controllers.
ControllerAdvice
is an annotation introduced in Spring three.two, and as the name suggests, is "Advice" for multiple controllers. It is used to enable a single ExceptionHandler
to be practical to multiple controllers. This manner we tin in just ane place ascertain how to care for such an exception and this handler will be called when the exception is thrown from classes that are covered past this ControllerAdvice
. The subset of controllers affected can defined by using the following selectors on @ControllerAdvice
: annotations()
, basePackageClasses()
, and basePackages()
. If no selectors are provided, then the ControllerAdvice
is applied globally to all controllers.
Then by using @ExceptionHandler
and @ControllerAdvice
, we'll be able to define a central bespeak for treating exceptions and wrapping them upwardly in an ApiError
object with better organization than the default Leap Boot error treatment machinery.
Handling Exceptions
The adjacent step is to create the class that will handle the exceptions. For simplicity, nosotros are calling information technology RestExceptionHandler
and it must extend from Jump Boot'due south ResponseEntityExceptionHandler
. Nosotros'll be extending ResponseEntityExceptionHandler
as it already provides some basic handling of Spring MVC exceptions, so we'll exist adding handlers for new exceptions while improving the existing ones.
Overriding Exceptions Handled In ResponseEntityExceptionHandler
If you have a look into the source code of ResponseEntityExceptionHandler
, y'all'll see a lot of methods chosen handle******()
like handleHttpMessageNotReadable()
or handleHttpMessageNotWritable()
. Let's outset run into how tin we extend handleHttpMessageNotReadable()
to handle HttpMessageNotReadableException
exceptions. Nosotros just have to override the method handleHttpMessageNotReadable()
in our RestExceptionHandler
class:
@Gild(Ordered.HIGHEST_PRECEDENCE) @ControllerAdvice public class RestExceptionHandler extends ResponseEntityExceptionHandler { @Override protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest asking) { Cord error = "Malformed JSON request"; return buildResponseEntity(new ApiError(HttpStatus.BAD_REQUEST, mistake, ex)); } private ResponseEntity<Object> buildResponseEntity(ApiError apiError) { return new ResponseEntity<>(apiError, apiError.getStatus()); } //other exception handlers beneath }
We accept declared that in case of a HttpMessageNotReadableException
being thrown, the error message will be "Malformed JSON request" and the error will exist encapsulated inside the ApiError
object. Below we tin see the answer of a Balance call with this new method overridden:
{ "apierror": { "condition": "BAD_REQUEST", "timestamp": "21-07-2017 03:53:39", "message": "Malformed JSON request", "debugMessage": "JSON parse error: Unrecognized token 'aaa': was expecting ('true', 'faux' or 'zip'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'aaa': was expecting ('true', 'false' or 'zilch')\n at [Source: java.io.PushbackInputStream@7b5e8d8a; line: iv, column: 17]" } }
Treatment Custom Exceptions
Now we'll meet how to create a method that handles an exception that is not still declared inside Spring Boot's ResponseEntityExceptionHandler
.
A common scenario for a Jump awarding that handles database calls is to have a call to detect a tape by its ID using a repository class. But if we expect into the CrudRepository.findOne()
method, we'll run into that it returns null
if an object is non found. That means that if our service just calls this method and returns directly to the controller, we'll get an HTTP code 200 (OK) even if the resource isn't constitute. In fact, the proper approach is to return a HTTP code 404 (Not Institute) as specified in the HTTP/i.1 spec.
To handle this case, we'll be creating a custom exception called EntityNotFoundException
. This one is a custom created exception and different from javax.persistence.EntityNotFoundException
, as information technology provides some constructors that ease the object cosmos, and one may choose to handle the javax.persistence
exception differently.
That said, let'southward create an ExceptionHandler
for this newly created EntityNotFoundException
in our RestExceptionHandler
class. To do that, create a method chosen handleEntityNotFound()
and annotate information technology with @ExceptionHandler
, passing the class object EntityNotFoundException.class
to it. This signalizes Spring that every time EntityNotFoundException
is thrown, Spring should call this method to handle information technology. When annotating a method with @ExceptionHandler
, it will take a wide range of car-injected parameters similar WebRequest
, Locale
and others as described here. We'll merely provide the exception EntityNotFoundException
itself as a parameter for this handleEntityNotFound
method.
@Lodge(Ordered.HIGHEST_PRECEDENCE) @ControllerAdvice public grade RestExceptionHandler extends ResponseEntityExceptionHandler { //other exception handlers @ExceptionHandler(EntityNotFoundException.class) protected ResponseEntity<Object> handleEntityNotFound( EntityNotFoundException ex) { ApiError apiError = new ApiError(NOT_FOUND); apiError.setMessage(ex.getMessage()); return buildResponseEntity(apiError); } }
Cracking! In the handleEntityNotFound()
method, we are setting the HTTP status code to NOT_FOUND
and using the new exception bulletin. Here is what the response for the Go /birds/2
endpoint looks like at present:
{ "apierror": { "condition": "NOT_FOUND", "timestamp": "21-07-2017 04:02:22", "message": "Bird was not found for parameters {id=ii}" } }
Conclusion
It is important to get in command of the exception handling so we can properly map those exceptions to the ApiError
object and provide important data that allows API clients to know what happened. The next footstep from hither would be to create more handler methods (the ones with @ExceptionHandler) for exceptions that are thrown inside the application code. There are more examples for some other common exceptions like MethodArgumentTypeMismatchException
, ConstraintViolationException
and others in the GitHub code.
Here are some additional resources that helped in the composition of this commodity:
-
Baeldung - Error handling for REST with Spring
-
Spring Web log - Exception handling in Spring MVC
Source: https://www.toptal.com/java/spring-boot-rest-api-error-handling
Post a Comment for "Error Uploading Sourcemaps to Sentry: Request Failed Because Api Url Was Incorrectly Formatted"