Gestión Rest de errores con Spring Boot
En esta nueva entrada de Refactorizando vamos a ver como podemos implementar en nuestros servicios la gestión Rest de errores con Spring Boot.
Durante las diferentes versiones de Spring, se han ido introduciendo mejoras en el tratamiento y gestión de errores. Desde el uso del @ExceptionHandler o el HandlerExceptionResolver en las versiones más viejas, a la introducción del @ControllerAdvice, o el uso del ResponseStatusException a partir de Spring 5.
Uso de @ExceptionHandler
El uso de @ExceptionHandler es una de las maneras más antiguas de realizar un control de errores en Spring. Esta anotación es usada a nivel de método dentro del controlador.
El problema principal que tiene esta anotación es que no es muy reutilizable, es decir, ese control del error, se lanzará únicamente en ese método.
public class CarController{ //... @ExceptionHandler({ ColorCarException.class, WheelCarException.class }) public void handleException() { // } }
Uso de HandlerExceptionResolver para la gestión Rest de errores con Spring Boot
El uso de HandlerExceptionResolver a diferencia de @ExceptionHandler, nos permite realizar una gestión de los errores para toda nuestra aplicación. En esta funcionalidad de basa el @ExceptionHandler.
Esta mejora fue introducida en la versión 3 de Spring y se encuentra activo por defecto en el DispatcherServlet.
El HandlerExceptionResolver, tiene diferentes implementaciones como el ExceptionHandlerExceptionResolver o el DefaultHandlerExceptionResolver que es capaz de gestionar y devolver una serie de errores por defecto de Spring, que son errores 4XX y 5XX. Esta gestión del error devuelve únicamente el código de error, sin nada en el body, a no ser que se realice a través de un ModelAndView.
Por otro lado, tenemos también el ResponseStatusExceptionResolver, que es bastante usado por la simplicidad de devolver status code en las respuestas. Por ejemplo:
@ResponseStatus(value = HttpStatus.NOT_FOUND) public class LocationNotFoundException extends RuntimeException { public MyResourceNotFoundException() { super(); } public LocationNotFoundException(String message, Throwable cause) { super(message, cause); } public LocationNotFoundException(String message) { super(message); } public LocationNotFoundException(Throwable cause) { super(cause); } }
Aunque esta solución es muy limpia y práctica, no podemos modificar el body para añadir un objeto a medida, y tenemos que añadir el @ResponseStatus en cada excepción.
Para poder resolver los problemas de añadir información en el body apareció el HandlerExceptionResolver. Esta clase nos va a dar la posibilidad e devolver status code exception y además de poder devover un body, vamos a verlo con un ejemplo:
@Component @Slf4j public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver { @Override protected ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { try { if (ex instanceof IllegalArgumentException) { return handleIllegalArgument( (IllegalArgumentException) ex, response, handler); } ... } catch (Exception handlerException) { logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException); } return null; } private ModelAndView handleIllegalArgument(IllegalArgumentException ex, HttpServletResponse response) throws IOException { response.sendError(HttpServletResponse.SC_CONFLICT); String accept = request.getHeader(HttpHeaders.ACCEPT); ... return new ModelAndView(); } }
Aunque estas mejoras fueron introducidas en Spring 3 y en su momento fueron muy importantes para el tratamiento de errores y excepciones, sin duda, la mayor aportación de Spring 3.2 fue la introducción de @ControllerAdvice
Gestión Rest de errores con @ControllerAdvice
Sin duda, esta es una de las mejores maneras de hacer una gestión Rest de errores en Spring Boot. Añade la funcionalidad de @ExceptionHandler con la cual podemos capturar la excepción que deseamos y tenemos un control en una única clase de las excepciones de nuestra aplicación.
Además vamos a poder añadir información y gestionar el body de respuesta y gestionar diferentes excepciones con la anotación @ExceptionHandler en un único método, vamos a verlo con un ejemplo:
@ControllerAdvice public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class }) public ResponseEntity<Object> handleConflict( RuntimeException ex, WebRequest request) { String bodyOfResponse = "Illegal argument exception error"; return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request); } @ExceptionHandler(CarException.class) public ResponseEntity<Object> CarException(final CarException e) { String bodyOfResponse = "Car exception"; return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request); } }
Gestión de errores en Spring Boot con ResponseStatusException
Esta forma de tratar los errores en Spring es la más nueva, ya que ha sido introducida en Spring 5. Particularmente me parece muy cómoda de usar, ya que puede ser aplicada en cualquier parte del código, pero me parece mucho más limpio el uso de @ControllerAdvice. Aunque podemos combinar ambas aproximaciones y usar de manera local esta solución.
@GetMapping(value = "/{id}") public Car findById(@PathVariable("id") Long id, HttpServletResponse response) { try { Car carById = RestPreconditions.checkFound(carRepository.findOne(id)); eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response)); return carById; } catch (CarNotFoundException ex) { throw new ResponseStatusException( HttpStatus.NOT_FOUND, "Car not found", exc); } }
Conclusión
En esta entrada hemos visto diferentes maneras de realizar la Gestión Rest de errores con Spring Boot, de una manera sencilla y fácil. Gestionar y devolver el status code así como información del error es imprescindible en las aplicaciones, así como en una buena arquitectura.
Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com y te ayudaremos encantados!