Spring boot + REST (Manejo de errores)


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:

Captura de pantalla 2017-09-11 a las 9.26.56 a.m.

  • 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:

Captura de pantalla 2017-09-11 a las 9.29.47 a.m.

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

Anuncios

3 Comentarios »

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s