Manejo global de errores
En una aplicación real siempre ocurren errores.
Por ejemplo:
- un usuario no existe
- datos inválidos
- error en base de datos
- petición incorrecta
Es muy importante manejar los errores correctamente para que la API devuelva respuestas claras.
En este capítulo veremos:
- excepciones en Spring
- crear excepciones personalizadas
- manejo global de errores
- respuestas de error profesionales
1. Qué ocurre si no manejamos errores #
Supongamos este código:
1@GetMapping("/users/{id}") 2public User getUser(@PathVariable Long id) { 3 4 return repository.findById(id).get(); 5 6}
Si el usuario no existe ocurrirá un error.
Respuesta típica:
1{ 2 "timestamp": "...", 3 "status": 500, 4 "error": "Internal Server Error" 5}
Este mensaje no es claro para el cliente.
2. Crear excepciones personalizadas #
Es mejor crear nuestras propias excepciones.
3. Ejemplo #
1public class UserNotFoundException extends RuntimeException { 2 3 public UserNotFoundException(String message) { 4 super(message); 5 } 6 7}
4. Usar la excepción en el service #
1public User getUser(Long id) { 2 3 return repository.findById(id) 4 .orElseThrow(() -> new UserNotFoundException("Usuario no encontrado")); 5 6}
Si el usuario no existe se lanzará esta excepción.
5. Manejo global de errores #
Para capturar excepciones usamos:
1@ControllerAdvice
Esto permite manejar errores en toda la aplicación.
6. Crear un manejador de errores #
1@ControllerAdvice 2public class GlobalExceptionHandler { 3 4}
7. Manejar una excepción específica #
Ejemplo:
1@ExceptionHandler(UserNotFoundException.class) 2public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) { 3 4 return ResponseEntity 5 .status(HttpStatus.NOT_FOUND) 6 .body(ex.getMessage()); 7 8}
8. Resultado #
Petición:
1GET /users/10
Respuesta:
1404 Not Found 2Usuario no encontrado
9. Manejar múltiples excepciones #
Podemos manejar diferentes errores.
Ejemplo:
1@ExceptionHandler(Exception.class) 2public ResponseEntity<String> handleGeneralError(Exception ex) { 3 4 return ResponseEntity 5 .status(HttpStatus.INTERNAL_SERVER_ERROR) 6 .body("Error interno del servidor"); 7 8}
Esto captura errores inesperados.
10. Crear un objeto de error #
En APIs profesionales es mejor devolver errores estructurados.
11. Clase ErrorResponse #
1public class ErrorResponse { 2 3 private int status; 4 private String message; 5 private String timestamp; 6 7}
12. Respuesta de error estructurada #
Ejemplo de respuesta:
1{ 2 "status": 404, 3 "message": "Usuario no encontrado", 4 "timestamp": "2026-01-10T12:00:00" 5}
Esto es mucho más profesional.
13. Implementación #
1@ExceptionHandler(UserNotFoundException.class) 2public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) { 3 4 ErrorResponse error = new ErrorResponse(); 5 6 error.setStatus(404); 7 error.setMessage(ex.getMessage()); 8 error.setTimestamp(LocalDateTime.now().toString()); 9 10 return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); 11 12}
14. Manejar errores de validación #
Cuando usamos @Valid, Spring lanza:
1MethodArgumentNotValidException
Podemos capturarlo.
15. Ejemplo #
1@ExceptionHandler(MethodArgumentNotValidException.class) 2public ResponseEntity<Map<String,String>> handleValidation( 3 MethodArgumentNotValidException ex) { 4 5 Map<String,String> errors = new HashMap<>(); 6 7 ex.getBindingResult().getFieldErrors() 8 .forEach(error -> 9 errors.put(error.getField(), error.getDefaultMessage())); 10 11 return ResponseEntity.badRequest().body(errors); 12 13}
16. Ejemplo respuesta de validación #
1{ 2 "name": "El nombre es obligatorio", 3 "email": "Email inválido" 4}
Esto facilita mucho el trabajo del frontend.
17. Códigos HTTP recomendados #
| código | uso |
|---|---|
| 200 | éxito |
| 201 | creado |
| 400 | petición incorrecta |
| 401 | no autorizado |
| 403 | acceso prohibido |
| 404 | recurso no encontrado |
| 500 | error servidor |
18. Ejemplo completo #
Excepción #
1public class UserNotFoundException extends RuntimeException { 2 3 public UserNotFoundException(String message) { 4 super(message); 5 } 6 7}
Service #
1public User getUser(Long id) { 2 3 return repository.findById(id) 4 .orElseThrow(() -> new UserNotFoundException("Usuario no encontrado")); 5 6}
Handler #
1@ControllerAdvice 2public class GlobalExceptionHandler { 3 4 @ExceptionHandler(UserNotFoundException.class) 5 public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) { 6 7 return ResponseEntity.status(404).body(ex.getMessage()); 8 9 } 10 11}
19. Buenas prácticas #
Nunca devolver errores genéricos #
❌
1Error interno
✔
1Usuario no encontrado
Usar excepciones específicas #
Ejemplo:
1UserNotFoundException 2OrderNotFoundException
Usar manejo global #
Evita repetir código en controllers.
Resumen del tema
Conceptos clave #
- Manejo Centralizado de Excepciones: anotaciones
@RestControllerAdvice(o@ControllerAdvice) junto con@ExceptionHandler(ExceptionClass.class)para interceptar errores de cualquier controlador. - Excepciones de Dominio Personalizadas: clases que extienden
RuntimeException(UserNotFoundException,ResourceAlreadyExistsException) lanzadas desde la capa de servicio conorElseThrow(). - Estructura Estándar de Respuesta de Error: DTO consistente con atributos semánticos (
timestamp,status,error,message,path) siguiendo o adaptando el estándar RFC 7807 (Problem Details). - Captura de Fallos de Validación: intercepción de
MethodArgumentNotValidExceptionmapeando cada campo inválido con su mensaje de restricción.
Qué debes recordar #
Centraliza el tratamiento de excepciones con @RestControllerAdvice y devuelve objetos JSON estructurados con los códigos de estado HTTP semánticos (400, 404, 409, 500).