Spring Boot – como capturar las excepciones y retornar un json estandar

En una aplicacion Spring en la cual exponemos endpoints Rest para que los clientes la consuman contamos con un manejador de excepciones de forma global para toda tu app.

Esto nos da la flexibilidad de decidir que devolver al cliente para cada tipo de exception.

En este ejemplo te muestro como capturar una excepción cuando el cliente consume tu api y el body que te ha enviado no es válido.

En el caso de recibir un body inválido Spring crea una excepción MethodArgumentNotValidException y retorna un json que seguramente sobra para el cliente o información que te gustaría estandarizar para todos los errores de forma más acorde para tu api.

El modelo que representa el body que vas a recibir


public class User {

    @JsonProperty
    @NotBlank
    private String userName;

    @JsonProperty
    @NotBlank
    private String lastName;

    @JsonProperty
    @NotNull
    private Gender gender;

    @JsonProperty
    @Min(value = 18)
    @Max(value = 150)
    @NotNull
    private Integer age;

    @JsonProperty
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
    @Past
    @NotNull
    private Date dateOfBirth;

Resultado de una exception MethodArgumentNotValidException

Este es el resultado que recibes cuando Spring lanza una excepción al recibir un body inválido en un post. En este ejemplo se omite un tag requerido “dateOfBirth” en el body y por esta razón se lanza la excepción.

¿Cómo debes capturar esta excepción y devolver algo a tu gusto?

Creas la clase que representará la respuesta json

Defines tu clase con los json property que deseas mostrar como retorno del error. Suponemos que deseas devolver tres atributos simples, el mensaje, el status code, y la url.

import com.fasterxml.jackson.annotation.JsonProperty;

public class ErrorInfo {

   @JsonProperty("message")
   private String message;
   @JsonProperty("status_code")
   private int statusCode;
   @JsonProperty("uri")
   private String uriRequested;

   public ErrorInfo(ApiException exception, String uriRequested) {
       this.message = exception.getMessage();
       this.statusCode = exception.getStatusCode().value();
       this.uriRequested = uriRequested;
   }

   public ErrorInfo(int statusCode, String message, String uriRequested) {
       this.message = message;
       this.statusCode = statusCode;
       this.uriRequested = uriRequested;
   }

   public String getMessage() {
       return message;
   }

   public int getStatusCode() {
       return statusCode;
   }

   public String getUriRequested() {
       return uriRequested;
   }

}

Creas el manejador global de excepciones ControllerAdvice

Para capturar este error debes crear una clase anotada @ControllerAdvice allí definirás cada tipo de error que deseas manejar @ExceptionHandler. Este método recibe el request y la excepción en particular. Puedes manejar todas las excepciones que desees.

Para nuestro caso:

@ControllerAdvice
public class ErrorHandler {

   @ExceptionHandler(MethodArgumentNotValidException.class)
   public ResponseEntity<ErrorInfo> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
       ErrorInfo errorInfo = new ErrorInfo(HttpStatus.BAD_REQUEST.value(), e.getMessage(), request.getRequestURI());
       return new ResponseEntity<>(errorInfo, HttpStatus.BAD_REQUEST);
   }
}

Si pruebas el resultado verás esta respuesta, algo mejor, pero todavia el mensaje está poco claro.

Mejorar el mensaje

Para hacer más legible el mensaje puedes leer cada uno de los FieldError que son devueltos por MethodArgumentNotValidException.getBindingResult() para armar un string más legible.

@ControllerAdvice
public class ErrorHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorInfo> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {

        // get spring errors
        BindingResult result = e.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();

        // convert errors to standard string
        StringBuilder errorMessage = new StringBuilder();
        fieldErrors.forEach(f -> errorMessage.append(f.getField() + " " + f.getDefaultMessage() +  " "));

        // return error info object with standard json
        ErrorInfo errorInfo = new ErrorInfo(HttpStatus.BAD_REQUEST.value(), errorMessage.toString(), request.getRequestURI());
        return new ResponseEntity<>(errorInfo, HttpStatus.BAD_REQUEST);

    }

}

Observa el resultado nuevamente al enviar un post que no válida:

Conclusión

Puedes hacer uso de ControllerAdvice para capturar todas las excepciones de tu api y devolver un formato json estandarizado para toda tu aplicación.

Descarga este código completo desde GitHub

Hi! If you find my posts helpful, please support me by inviting me for a coffee :)

Ver también