Gestión de errores RestControllerAdvice

Intermedio
Spring Boot
Spring Boot
Actualizado: 04/05/2026

@ExceptionHandler y @RestControllerAdvice

flowchart TB
    REQ[Petición HTTP] --> CTRL[Método controlador]
    CTRL --> SVC[Servicio o repositorio]
    SVC -.lanza.-> EX[Excepción Java]
    EX --> RESOLVE[ExceptionHandlerExceptionResolver]
    RESOLVE --> LOCAL{Existe ExceptionHandler local}
    LOCAL -- sí --> HLOC[Método del propio controlador]
    LOCAL -- no --> ADV[RestControllerAdvice global]
    ADV --> H1[handleEntityNotFound 404]
    ADV --> H2[handleValidation 400]
    ADV --> H3[handleAccessDenied 403]
    ADV --> H4[handleGeneric 500]
    H1 --> JSON[ResponseEntity con ProblemDetail RFC 7807]
    H2 --> JSON
    H3 --> JSON
    H4 --> JSON
    HLOC --> JSON

La gestión centralizada de errores en Spring Boot permite manejar excepciones de forma consistente en toda la aplicación REST. En lugar de duplicar código de manejo de errores en cada controlador, Spring proporciona un mecanismo elegante para centralizar esta responsabilidad mediante anotaciones específicas.

Fundamentos de @ExceptionHandler

La anotación @ExceptionHandler permite definir métodos que capturan y procesan excepciones específicas dentro de un controlador. Este enfoque básico funciona bien cuando necesitas manejar errores particulares de un solo controlador.

@RestController
@RequestMapping("/api/usuarios")
public class UsuarioController {
    
    @Autowired
    private UsuarioService usuarioService;
    
    @GetMapping("/{id}")
    public Usuario obtenerUsuario(@PathVariable Long id) {
        return usuarioService.buscarPorId(id);
    }
    
    // Manejo local de excepciones
    @ExceptionHandler(UsuarioNoEncontradoException.class)
    public ResponseEntity<String> manejarUsuarioNoEncontrado(UsuarioNoEncontradoException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body("Usuario no encontrado: " + ex.getMessage());
    }
}

El método anotado con @ExceptionHandler intercepta automáticamente las excepciones del tipo especificado que ocurran en cualquier método del mismo controlador. Sin embargo, este enfoque tiene limitaciones cuando necesitas aplicar el mismo manejo de errores en múltiples controladores.

Introducción a @RestControllerAdvice

La anotación @RestControllerAdvice extiende las capacidades de manejo de errores a nivel global de la aplicación. Esta anotación combina @ControllerAdvice con @ResponseBody, lo que la hace ideal para APIs REST que devuelven respuestas JSON.

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(UsuarioNoEncontradoException.class)
    public ResponseEntity<ErrorResponse> manejarUsuarioNoEncontrado(UsuarioNoEncontradoException ex) {
        ErrorResponse error = new ErrorResponse(
            "USUARIO_NO_ENCONTRADO",
            ex.getMessage(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
    
    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<ErrorResponse> manejarValidacion(ValidationException ex) {
        ErrorResponse error = new ErrorResponse(
            "DATOS_INVALIDOS",
            ex.getMessage(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
}

Creación de respuestas de error estructuradas

Para mantener consistencia en las respuestas de error, es recomendable crear una clase que represente la estructura estándar de los errores:

public class ErrorResponse {
    private String codigo;
    private String mensaje;
    private LocalDateTime timestamp;
    
    public ErrorResponse(String codigo, String mensaje, LocalDateTime timestamp) {
        this.codigo = codigo;
        this.mensaje = mensaje;
        this.timestamp = timestamp;
    }
    
    // Getters y setters
    public String getCodigo() { return codigo; }
    public void setCodigo(String codigo) { this.codigo = codigo; }
    
    public String getMensaje() { return mensaje; }
    public void setMensaje(String mensaje) { this.mensaje = mensaje; }
    
    public LocalDateTime getTimestamp() { return timestamp; }
    public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
}

Manejo de múltiples tipos de excepciones

Un controlador de excepciones global puede manejar diferentes tipos de errores con métodos específicos para cada caso:

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    // Errores de validación de Spring
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> manejarValidacionCampos(MethodArgumentNotValidException ex) {
        StringBuilder mensaje = new StringBuilder("Errores de validación: ");
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            mensaje.append(error.getField()).append(" - ").append(error.getDefaultMessage()).append("; ")
        );
        
        ErrorResponse error = new ErrorResponse(
            "VALIDACION_FALLIDA",
            mensaje.toString(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
    
    // Errores de conversión de tipos
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<ErrorResponse> manejarTipoIncorrecto(MethodArgumentTypeMismatchException ex) {
        String mensaje = String.format("El parámetro '%s' debe ser de tipo %s", 
            ex.getName(), ex.getRequiredType().getSimpleName());
        
        ErrorResponse error = new ErrorResponse(
            "TIPO_PARAMETRO_INCORRECTO",
            mensaje,
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
    
    // Captura general para excepciones no manejadas
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> manejarErrorGeneral(Exception ex) {
        ErrorResponse error = new ErrorResponse(
            "ERROR_INTERNO",
            "Ha ocurrido un error inesperado",
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

Acceso a información de la petición

Los métodos de manejo de excepciones pueden acceder a información adicional sobre la petición HTTP que causó el error:

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(AccesoDenegadoException.class)
    public ResponseEntity<ErrorResponse> manejarAccesoDenegado(
            AccesoDenegadoException ex, 
            HttpServletRequest request) {
        
        String mensaje = String.format("Acceso denegado al recurso: %s %s", 
            request.getMethod(), request.getRequestURI());
        
        ErrorResponse error = new ErrorResponse(
            "ACCESO_DENEGADO",
            mensaje,
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
    }
}

Configuración de alcance específico

@RestControllerAdvice permite limitar su alcance a controladores específicos mediante atributos de configuración:

// Solo aplica a controladores en el paquete especificado
@RestControllerAdvice(basePackages = "com.ejemplo.api.controladores")
public class ApiExceptionHandler {
    // Métodos de manejo de excepciones
}

// Solo aplica a controladores específicos
@RestControllerAdvice(assignableTypes = {UsuarioController.class, ProductoController.class})
public class EspecificoExceptionHandler {
    // Métodos de manejo de excepciones
}

Esta flexibilidad permite crear diferentes estrategias de manejo de errores para distintas partes de la aplicación, manteniendo la separación de responsabilidades y facilitando el mantenimiento del código.

Caso B2B: contratos de error en banca y AAPP

En entidades bancarias que exponen APIs a partners (open banking PSD2, agregadores), el contrato de error es tan importante como el de éxito. Vuestro equipo modela un RestControllerAdvice global que produce ProblemDetail según RFC 7807 (estándar adoptado oficialmente por Spring Boot 3 con org.springframework.http.ProblemDetail). El partner consumidor recibe siempre la misma estructura: type, title, status, detail, instance, más los campos extendidos traceId y errorCode para correlacionar con el log centralizado. La organización gana porque cualquier cambio se documenta en una única clase y los partners no tienen que reaprender el formato.

En administraciones públicas con servicios SCSP/NTI, los errores deben respetar esquemas XSD oficiales. Centralizar la conversión de Exception → respuesta en un advice permite mapear cualquier excepción interna a uno de los códigos del catálogo oficial, garantizando interoperabilidad entre entidades.

En telco con APIs de provisionamiento, el patrón @RestControllerAdvice con métodos específicos por tipo de excepción permite distinguir entre errores de negocio (4xx con mensaje detallado) y errores de infraestructura (5xx genéricos). El equipo de SRE configura alertas por código de error: si el ratio de INTERNAL_ERROR supera el 1%, salta una incidencia automática en PagerDuty.

En retail con pasarelas de pago, los advices manejan timeouts a la red de pagos transformándolos en 503 Service Unavailable con cabecera Retry-After, permitiendo al cliente reintentar de forma controlada.

Versiones (2025)

Spring Boot 4.0 (estable desde abril 2026) es la versión actual; Spring Boot 3.4 (noviembre 2024) y 3.5 (mayo 2025) son la rama 3.x en mantenimiento. Spring Boot 4 requiere Java 21+ y Jakarta EE 9+ (paquete jakarta.*, no javax.*). El soporte nativo a ProblemDetail llegó en Spring Framework 6.0 (parte de Spring Boot 3.0). Para vuestro equipo en proyectos B2B, conviene generar ProblemDetail directamente:

@ExceptionHandler(EntityNotFoundException.class)
public ProblemDetail handle(EntityNotFoundException ex) {
    var pd = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
    pd.setType(URI.create("https://api.acme.com/errors/not-found"));
    pd.setProperty("errorCode", "ENTITY_NOT_FOUND");
    pd.setProperty("traceId", MDC.get("traceId"));
    return pd;
}

Spring Boot 3 también ofrece ResponseEntityExceptionHandler como base con métodos protegidos sobrescribibles para excepciones del framework (validación, parámetros faltantes).

Anti-patrones y pitfalls

Capturar Exception.class y devolver detalles del stacktrace al cliente. Filtrar mensajes internos al exterior es una vulnerabilidad de divulgación de información. En entornos productivos, devolved un código de error genérico y registrad el detalle en el log con un traceId correlacionable.

Múltiples @RestControllerAdvice con orden no especificado. Si vuestro proyecto tiene varios advices (uno por dominio), el orden de evaluación es indeterminado. Usad @Order(...) para fijar precedencia.

Olvidar mapear excepciones de validación. MethodArgumentNotValidException, ConstraintViolationException, HttpMessageNotReadableException son las más comunes y, sin handlers específicos, devuelven 500 con mensajes Spring por defecto.

Construir ResponseEntity<Map<String, Object>> ad-hoc. Es propenso a divergir en cada handler. Usad un DTO estable o ProblemDetail directamente.

No registrar el error en log antes de devolver. Los advices que solo construyen la respuesta sin loguear pierden trazabilidad. Logueas en ERROR o WARN según severidad.

Devolver 200 con un campo error: true. Anti-patrón clásico que confunde a clientes y proxies. Respetad los códigos HTTP estándar.

Comparativa con alternativas

Frente a HandlerExceptionResolver legacy, @RestControllerAdvice es declarativo y testeable. La interfaz histórica sigue funcionando pero es más verbosa.

Frente a WebFlux con @ExceptionHandler reactivo, los advices funcionan en ambos modelos (sirviendo Mono<ResponseEntity<...>>). Para servicios reactivos, considerad además WebExceptionHandler para excepciones a nivel de filtro.

Frente a filtros Servlet que capturan errores, los advices se ejecutan en el contexto del controlador y tienen acceso al binding del request (parámetros, body deserializado). Los filtros son más adecuados para errores antes del routing (autenticación fallida, CORS).

Frente a manejo de errores en cada controlador, el advice global elimina duplicación. Mantened un advice por agregado/dominio si la aplicación es grande, no uno solo gigante.

Documentación oficial

La referencia es la documentación de Spring Framework "Annotated Controllers → Exceptions" (docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-exceptionhandler.html) y de Spring Boot "Error Handling". Para ProblemDetail, consultad RFC 7807 y la sección "Error Responses" de Spring Framework. El libro "Spring Boot in Action" 3.ª edición de Craig Walls cubre los patrones recientes.

@RestControllerAdvice es la pieza central de cualquier API REST profesional con Spring Boot. Para vuestro equipo en proyectos B2B, dominarlo —junto con ProblemDetail— es lo que separa una API casera de una integrable con partners exigentes.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Spring Boot

Documentación oficial de Spring Boot
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Spring Boot es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Spring Boot

Explora más contenido relacionado con Spring Boot y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Comprender el uso básico de @ExceptionHandler para manejar excepciones en controladores individuales. Aprender a centralizar el manejo de errores en toda la aplicación con @RestControllerAdvice. Diseñar respuestas de error estructuradas y consistentes para APIs REST. Implementar manejo de múltiples tipos de excepciones específicas y generales. Configurar el alcance de @RestControllerAdvice para aplicar el manejo de errores a controladores específicos o paquetes.