Uno de los puntos más importantes al diseñar un api rest es el manejo de errores, en este post se explicará como implementar el manejo de errores utilizando Spring Boot + Jersey. Se tomará como base el ejemplo Spring Boot + REST Jersey Part 1.
Paso 1 : Crear clases para representar los errores
El primer paso será crear las clases necesarias para representar los errores, en este caso se crearán dos clases Error y ServiceException.
ErrorMessage.java
package com.raidentrance.model; import java.io.Serializable; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; /** * @author raidentrance * */ @JsonInclude(Include.NON_NULL) public class ErrorMessage implements Serializable { private Integer httpStatus; private String message; private Integer code; private String developerMessage; private static final long serialVersionUID = 5318063708359922770L; public ErrorMessage() { } public ErrorMessage(ServiceException ex) { this.httpStatus = ex.getHttpStatus(); this.message = ex.getMessage(); this.code = ex.getCode(); } public ErrorMessage(Integer httpStatus, String message, Integer code) { super(); this.httpStatus = httpStatus; this.message = message; this.code = code; } public Integer getHttpStatus() { return httpStatus; } public void setHttpStatus(Integer httpStatus) { this.httpStatus = httpStatus; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getDeveloperMessage() { return developerMessage; } public void setDeveloperMessage(String developerMessage) { this.developerMessage = developerMessage; } }
La clase error será utilizada para representar los errores en formato JSON, y contiene los siguientes atributos:
- httpStatus: Contendrá el status http que devolverá la petición REST
- message: Se utilizará para mostrar un pequeño mensaje de error que describa el problema
code : Representa algún código de error que pueda ser representativo para una aplicación
developerMessage: Se puede especificar la excepción generada para que un desarrollador pueda detectar de forma simple el problema
La anotación @JsonInclude(Include.NON_NULL) indica que si algún atributo es nulo no se mostrará en la respuesta.
ServiceException.java
/** * */ package com.raidentrance.model; /** * @author raidentrance * */ public class ServiceException extends Exception{ private Integer httpStatus; private String message; private Integer code; private String developerMessage; private static final long serialVersionUID = -528134378438377740L; public ServiceException(Integer httpStatus, String message, Integer code,String developerMessage) { this.httpStatus = httpStatus; this.message = message; this.code = code; this.developerMessage=developerMessage; } public ServiceException(Integer httpStatus, String message, Integer code) { this.httpStatus = httpStatus; this.message = message; this.code = code; } public ServiceException(ErrorMessage errorMessage){ this.httpStatus = errorMessage.getHttpStatus(); this.message = errorMessage.getMessage(); this.code = errorMessage.getCode(); this.developerMessage=errorMessage.getDeveloperMessage(); } public String getDeveloperMessage() { return developerMessage; } public void setDeveloperMessage(String developerMessage) { this.developerMessage = developerMessage; } public Integer getHttpStatus() { return httpStatus; } public void setHttpStatus(Integer httpStatus) { this.httpStatus = httpStatus; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } }
La clase ServiceException es una excepción y será utilizada para arrojar los errores dentro de la aplicación, como se puede observar es posible crearla utilizando los parámetros necesarios para alimentar la clase ErrorMessage.
Paso 2 : Crear exception mappers
El siguiente paso es crear exception mappers, estos definirán el que se debe hacer en caso de algún tipo de excepción. Para este ejemplo se crearán 2 exception mappers uno en caso de que se produzca una excepción ServiceException y la otra en caso de que se genere una tipo Throwable en caso de cualquier otro tipo de error:
ServiceExceptionMapper.java
package com.raidentrance.error; import java.io.PrintWriter; import java.io.StringWriter; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import com.raidentrance.model.ErrorMessage; import com.raidentrance.model.ServiceException; /** * @author raidentrance * */ @Provider public class ServiceExceptionMapper implements ExceptionMapper<ServiceException> { @Override public Response toResponse(ServiceException ex) { ErrorMessage errorMessage = new ErrorMessage(); errorMessage.setCode(ex.getCode()); errorMessage.setMessage(ex.getMessage()); StringWriter errorStackTrace = new StringWriter(); ex.printStackTrace(new PrintWriter(errorStackTrace)); errorMessage.setDeveloperMessage(ex.toString()); return Response.status(ex.getHttpStatus()).entity(errorMessage).type(MediaType.APPLICATION_JSON).build(); } }
Para crear un exception mapper solo es necesario implementar la interfaz ExceptionMapper y especificar en el genérico el tipo de excepción que se manejará en este caso se utilizará ServiceException.
GenericExceptionMapper.java
package com.raidentrance.error; import java.io.PrintWriter; import java.io.StringWriter; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import org.springframework.http.HttpStatus; import com.raidentrance.model.ErrorMessage; /** * @author raidentrance * */ @Provider public class GenericExceptionMapper implements ExceptionMapper<Throwable> { public Response toResponse(Throwable ex) { ErrorMessage errorMessage = new ErrorMessage(); errorMessage.setMessage(ex.getMessage()); StringWriter errorStackTrace = new StringWriter(); ex.printStackTrace(new PrintWriter(errorStackTrace)); errorMessage.setDeveloperMessage(ex.toString()); return Response.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).entity(errorMessage) .type(MediaType.APPLICATION_JSON).build(); } }
Del mismo modo se creará otro exception mapper pero ahora para la clase Throwable, esto significa que cualquier otro tipo de error será atrapado por este exception handler y procesado, como se puede observar este exception mapper será utilizado para atrapar cualquier tipo de excepción que no sea manejada por la aplicación por esto devolveremos como status http un 500 que significa Internal Server Error.
Paso 3 : Registrando los Exception Mappers
Una vez que se crearon los Exception Mappers deben ser registrados en la aplicación. Para hacer esto se deben definir del mismo modo que los endpoints en la clase JerseyConfig.
JerseyConfig
/** * */ package com.raidentrance.config; import org.glassfish.jersey.server.ResourceConfig; import org.springframework.stereotype.Component; import com.raidentrance.error.GenericExceptionMapper; import com.raidentrance.error.ServiceExceptionMapper; import com.raidentrance.resource.UserResource; /** * @author raidentrance * */ @Component public class JerseyConfig extends ResourceConfig { public JerseyConfig() { register(UserResource.class); register(ServiceExceptionMapper.class); register(GenericExceptionMapper.class); } }
Paso 4 : Lanzar excepciones de prueba
Para probar que los exception mappers funcionan de forma correcta crearemos dos endpoints uno arrojará una ServiceException y el otro una NullPointerException:
- Endpoint que devuelve un ServiceException:
@GET @Path("/error") public Response sampleError() throws ServiceException { throw new ServiceException(HttpStatus.NOT_FOUND.value(), "Sample Error Message", 1); }
Si ejecutamos el endpoint anterior, la respuesta será la siguiente con un status HTTP 404:
- Endpoint que devuelve una NullPointerException:
@GET @Path("/error/generic") public Response sampleGenericError() { throw new NullPointerException(); }
Si ejecutamos el endpoint anterior, la respuesta será la siguiente con un status HTTP 500:
Si deseas aprender más sobre servicios web, recomendamos los siguientes libros:
Puedes encontrar el código completo en el siguiente enlace : https://github.com/raidentrance/spring-boot-example/tree/part5-adding-error-handling
Autor: Alejandro Agapito Bautista
Twitter: @raidentrance
Contacto:raidentrance@gmail.com
Recomiendo bastante utilizar anotaciones de Lombok para la construccion de getters y setters, tus clases quedarian muy simples y te ahorras muchas lineas de codigo. Te dejo el link para que le eches un ojo: https://projectlombok.org/
Me gustaMe gusta
Otra buena explicacion de lo que es Lombok, sus caracteristicas y algunas de sus anotaciones http://jnb.ociweb.com/jnb/jnbJan2010.html
Me gustaMe gusta
Muchas gracias por el aporte 😀 esta muy padre el proyecto
Me gustaMe gusta