Manejar excepciones en Spring Boot deja de ser un dolor cuando entiendes cómo capturar errores de forma centralizada y devolver respuestas claras al cliente. Aquí aprendes a usar la anotación @RestControllerAdvice junto con excepciones personalizadas para transformar un error 500 confuso en un 400 informativo, ideal si construyes APIs REST profesionales.
¿Por qué controlar excepciones en una API REST?
Una API no solo debe funcionar en el camino feliz. Debe responder bien cuando algo falla: una validación rota, un recurso que no existe o un dato duplicado. Y aquí viene lo interesante: si dejas que Spring devuelva un error 500 genérico, quien consume tu API se queda sin pistas.
Imagina que tu base de datos tiene una restricción unique sobre la columna título de la tabla películas. Si alguien intenta guardar Son como niños y esa película ya existe, la base de datos lanza una violación de clave única y tu API responde con un error 500 que no dice nada útil [01:00].
¿Qué hace @RestControllerAdvice en Spring Boot? Marca una clase como manejador global de excepciones para controladores REST. Captura las excepciones que lances en cualquier capa y las transforma en respuestas HTTP estructuradas.
¿Cómo validar un dato único antes de guardar en la base?
La validación empieza en el repositorio. Antes de hacer un save, debes preguntarle a la base si ya existe un registro con ese título. Para eso usas un query method de Spring Data JPA, que traduce automáticamente el nombre del método a SQL en tiempo de ejecución [02:00].
El método queda así dentro de tu interfaz CrudRepository:
java
MovieEntity findFirstByTitulo(String titulo);
Luego, en el método save de tu repositorio, validas:
java
if (this.crudMovieEntity.findFirstByTitulo(titulo) != null) {
throw new MovieAlreadyExistsException(titulo);
}
Si el resultado no es null, significa que la película ya existe y debes detener la ejecución lanzando una excepción.
¿Dónde creo la excepción personalizada?
Dentro del paquete domain crea un subpaquete llamado exception y ahí una clase MovieAlreadyExistsException que herede de RuntimeException [03:30]. El constructor recibe el título de la película para construir un mensaje claro:
java
public class MovieAlreadyExistsException extends RuntimeException {
public MovieAlreadyExistsException(String movieTitle) {
super("La película " + movieTitle + " ya existe");
}
}
Siempre añade el sufijo Exception a la clase. Así, quien lea el código entiende de inmediato que se trata de una excepción.
¿Cómo capturar la excepción y devolver una respuesta limpia?
Aquí entra la pieza clave. En la capa web crea otro paquete exception separado del de controller. Dentro, una clase llamada RestExceptionHandler anotada con @RestControllerAdvice [05:30].
Esta anotación le dice a Spring que esta clase es la encargada de interceptar excepciones que ocurran en cualquier controlador.
¿Qué estructura debe tener la respuesta de error?
Necesitas un tipo que represente el error. Un record funciona perfecto porque es inmutable y minimalista:
java
public record Error(String type, String message) {}
Luego defines el método que captura la excepción:
java
@ExceptionHandler(MovieAlreadyExistsException.class)
public ResponseEntity<Error> handleException(MovieAlreadyExistsException ex) {
Error error = new Error("movie-already-exists", ex.getMessage());
return ResponseEntity.badRequest().body(error);
}
Fíjate en dos detalles. Primero, le indicas a @ExceptionHandler qué tipo de excepción debe controlar usando .class. Segundo, no devuelves un OK, sino un badRequest(), que corresponde al código HTTP 400.
¿Cuál es la diferencia entre un error 500 y un 400? Un 500 indica un fallo no controlado del servidor. Un 400 indica que la petición del cliente tiene un problema, como datos inválidos o duplicados. El 400 es mucho más informativo.
¿Qué pasa cuando vuelves a probar la petición?
Al reiniciar la aplicación y enviar desde Postman la misma petición POST para crear Son como niños, ya no recibes el 500 silencioso. Ahora la respuesta es un 400 con un cuerpo claro [07:30]:
- type: movie-already-exists.
- message: La película Son como niños ya existe.
Esa es la diferencia entre una API que solo funciona y una API profesional. El consumidor entiende qué pasó y puede actuar.
¿Puedo capturar varias excepciones en la misma clase? Sí. Dentro de la clase anotada con @RestControllerAdvice puedes definir múltiples métodos, cada uno con su propio @ExceptionHandler apuntando a una excepción distinta.
¿Qué reto puedes resolver tú?
Maneja el caso en que alguien intente editar o eliminar una película que no existe. Crea una excepción MovieNotFoundException, lánzala desde el repositorio cuando no encuentres el registro y captúrala en tu RestExceptionHandler devolviendo un 404. Comparte tu solución en los comentarios y cuéntame cómo estructuraste tu paquete de excepciones.