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.