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 actualstatus– código HTTPerror– frase razón del estadoexception– clase de la excepciónmessage– mensaje del errorpath– 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– produceModelAndViewpara peticiones con cabeceraAccept: text/html.error– devuelveResponseEntity<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:
- Plantillas (Thymeleaf, Freemarker, etc.) en
error/{status}. - Recursos estáticos en
/error/{status}.html. - 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.htmloerror/5xx.htmlensrc/main/resources/templates. Tienen prioridad los códigos exactos. - Sin motor de plantillas: ubicar los archivos
error/404.htmlensrc/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.