Gestión de errores RestControllerAdvice

Intermedio
SpringBoot
SpringBoot
Actualizado: 13/06/2025

@ExceptionHandler y @RestControllerAdvice

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.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en SpringBoot

Documentación oficial de SpringBoot
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, SpringBoot 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 SpringBoot

Explora más contenido relacionado con SpringBoot 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.

Cursos que incluyen esta lección

Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje