Manejo de errores en Spring Boot y su personalización completa

Spring Boot ofrece una gestión de errores predefinida que se adapta al tipo de cliente: para navegadores devuelve una página HTML de error, mientras que para clientes REST (como Postman) responde con un objeto JSON. A continuación se explica el funcionamiento interno y cómo personalizar tanto las páginas como los datos de error.

Mecanismo predeterminado de errores

La configuración automática ErrorMvcAutoConfiguration registra varios componentes clave:

1. DefaultErrorAttributes

Se encarga de ensamblar el mapa de atributos del error. Se registra así:

@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes();
}

Internamente, DefaultErrorAttributes implementa ErrorAttributes y HandlerExceptionResolver. Su método getErrorAttributes construye un LinkedHashMap con las siguientes claves:

  • timestamp – fecha actual
  • status – código HTTP
  • error – frase razón del estado
  • exception – clase de la excepción
  • message – mensaje del error
  • path – ruta solicitada

Si la excepción es de validación (BindingResult), también se agrega errors.

2. BasicErrorController

Controlador que mapea la ruta /error. Tiene dos métodos:

  • errorHtml – produce ModelAndView para peticiones con cabecera Accept: text/html.
  • error – devuelve ResponseEntity<Map<String,Object>> para el resto de clientes.

Ejemplo del método HTML:

@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
                              HttpServletResponse response) {
    HttpStatus status = getStatus(request);
    Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
    response.setStatus(status.value());
    ModelAndView mav = resolveErrorView(request, response, status, model);
    return (mav != null) ? mav : new ModelAndView("error", model);
}

3. ErrorPageCustomizer

Registra la página de error en el contenedor web (como web.xml) apuntando a /error.

@Bean
public ErrorPageCustomizer errorPageCustomizer() {
    return new ErrorPageCustomizer(this.serverProperties);
}

4. DefaultErrorViewResolver

Resuelve la vista de error basándose en el código de estado. Primero intenta con el código exacto (p.ej. 404), y si no, con la familia (4xx, 5xx). Busca en:

  1. Plantillas (Thymeleaf, Freemarker, etc.) en error/{status}.
  2. Recursos estáticos en /error/{status}.html.
  3. Si nada, usa la vista blanca predeterminada de Spring Boot.

Personalización de las respuestas de error

Páginas de error personalizadas

Para personalizar la página:

  • Con motor de plantilas: colocar arcihvos como error/404.html o error/5xx.html en src/main/resources/templates. Tienen prioridad los códigos exactos.
  • Sin motor de plantillas: ubicar los archivos error/404.html en src/main/resources/static.

Personalización de los datos de error

Hay tres enfoques:

A. Usar un manejador de excepciones con @ControllerAdvice

Definir un método que devuelva JSON:

@ControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String, Object> handleUserNotExist(UserNotExistException ex) {
        Map<String, Object> result = new HashMap<>();
        result.put("code", "user.notexist");
        result.put("detail", ex.getMessage());
        return result;
    }
}

Este método siempre devuelve JSON, incluso para navegadores.

B. Reenviar a /error para que Spring Boot decida el formato

En el manejador, establecer el código de estado y reenviar:

@ExceptionHandler(UserNotExistException.class)
public String handleAndForward(UserNotExistException ex, HttpServletRequest request) {
    request.setAttribute("javax.servlet.error.status_code", 500);
    Map<String, Object> extra = new HashMap<>();
    extra.put("code", "user.notexist");
    extra.put("message", "Usuario no encontrado");
    request.setAttribute("ext", extra);
    return "forward:/error";
}

Los datos personalizados no se incluirán automáticamente en la respuesta de /error.

C. Extender DefaultErrorAttributes (recomendado)

Crear un bean que herede de DefaultErrorAttributes y sobrescriba getErrorAttributes para agregar campos adicionales.

@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
                                                    boolean includeStackTrace) {
        Map<String, Object> attrs = super.getErrorAttributes(requestAttributes, includeStackTrace);
        attrs.put("company", "MiEmpresa");
        Map<String, Object> extra = (Map<String, Object>) requestAttributes.getAttribute("ext", 0);
        if (extra != null) {
            attrs.put("ext", extra);
        }
        return attrs;
    }
}

Luego, en el manejador se almacenan los datos en el atributo ext del request, y al reenviar a /error, CustomErrorAttributes los incluirá en el mapa final.

Ejemplo de página de error con Thymeleaf

Archivo error/5xx.html:

<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
  <h1>Estado: [[${status}]]</h1>
  <h2>Marca de tiempo: [[${timestamp}]]</h2>
  <h2>Excepción: [[${exception}]]</h2>
  <h2>Mensaje: [[${message}]]</h2>
  <h2>Código adicional: [[${ext.code}]]</h2>
  <h2>Detalle adicional: [[${ext.message}]]</h2>
</main>

Con esta configuración, los navegadores verán la página HTML con los datos personalizados, mientras que Postman recibirá un JSON con la misma información.

Etiquetas: spring-boot error-handling exception-handler error-controller default-error-attributes

Publicado el 6-17 23:59