Manejo de excepciones¶
Un mecanismo consistente para manejar excepciones evita duplicación, mejora la trazabilidad y traduce errores internos a respuestas HTTP comprensibles para los clientes.
Objetivos¶
- Centralizar la conversión de excepciones a respuestas HTTP.
- Mantener los controladores limpios y delegar el formato de errores a una capa común.
- Registrar (log) errores y enviar la información mínima necesaria al cliente.
Herramientas en Spring¶
@ControllerAdvice: componente global que puede interceptar excepciones lanzadas por controladores.@ExceptionHandler: indica el tipo de excepción a manejar y el método que genera la respuesta.ResponseEntityExceptionHandler: clase base útil para manejar errores del framework (ej.MethodArgumentNotValidException).@ResponseStatus(opcional): permite asociar unHttpStatusa una excepción concreta.
Patrón recomendado¶
- Definir un DTO de error (por ejemplo
ErrorDto) que incluya: código, mensaje, timestamp y detalles opcionales. - Implementar un
@ControllerAdviceque capture excepciones conocidas y devuelvaResponseEntity<ErrorDto>con elHttpStatusadecuado. - Registrar errores con niveles adecuados (
WARNpara validaciones,ERRORpara fallos internos).
Ejemplo de ErrorDto¶
public class ErrorDto {
private String code;
private String message;
private Instant timestamp = Instant.now();
private List<String> details;
// constructores, getters y setters
}
Ejemplo de @ControllerAdvice¶
@ControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
private final Logger log = LoggerFactory.getLogger(ApiExceptionHandler.class);
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<ErrorDto> handleNotFound(EntityNotFoundException ex) {
log.debug("Recurso no encontrado: {}", ex.getMessage());
ErrorDto err = new ErrorDto("NOT_FOUND", ex.getMessage(), List.of());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(err);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorDto> handleBadRequest(BadRequestException ex) {
ErrorDto err = new ErrorDto("BAD_REQUEST", ex.getMessage(), List.of());
return ResponseEntity.badRequest().body(err);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
List<String> details = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.toList());
ErrorDto err = new ErrorDto("VALIDATION_ERROR", "Campos inválidos", details);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(err);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDto> handleAll(Exception ex) {
log.error("Error inesperado", ex);
ErrorDto err = new ErrorDto("INTERNAL_ERROR", "Ocurrió un error interno");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(err);
}
}
Consideraciones prácticas¶
- No devolver trazas completas en producción; suficiente mensaje amigable y un identificador para el soporte.
- Para APIs públicas, documentar los códigos de error y estructuras en la especificación (OpenAPI).
- Distinguir entre errores de cliente (4xx) y del servidor (5xx).
- Usar códigos de error internos (ej.
USER_NOT_FOUND) para facilitar el manejo por parte del cliente.
¿Quieres que genere también un ejemplo de ErrorDto en un archivo separado y que lo referencie desde el ControllerAdvice?