Manejo de excepciones en Spring Boot
Motivación
Se requiere un mecanismo global o centralizado basado en las excepciones, para poder manejar y traducir en errores entendibles hacia las integraciones (frontend y/o otros clientes).
Introducción
La gestión de excepciones en el sentido amplio de la palabra, se refiere a la captura de eventos de tipo errores que se producen en las aplicaciones durante su ejecución. Estos errores pueden ser de muchos tipos, desde fallas de red, errores en accesos a una base de datos, errores en tiempo de ejecución (runtime) o simplemente por validaciones de negocio.
El manejo de excepciones tiene como objetivo hacer más seguras y robustas las aplicaciones así como también tener un mecanismo para dar una respuesta elegante a los usuarios mejorando la confiabilidad y resistencia a fallas en las aplicaciones.
Usos
Manejo global de excepciones:
- ¿ Cuándo usarlo ?
- Cuando se necesita un formato de respuesta de error consistente para toda la aplicación.
- Para evitar exponer trazas de la aplicación con información relevante, de esta forma evitar posibles futuros atacantes.
- ¿ Cuándo usarlo ?
Manejo de excepciones en el controlador:
- Utilizar cuando se necesita una solución particular (distinta a la ofrecida por el manejo de errores globales), como por ejemplo, integraciones donde necesitamos adaptar nuestra respuesta.
Implementación
Para su implementación, Spring Boot nos provee algunas herramientas, las más comunes para este tipo de manejadores son @ControllerAdvice y de @RestControllerAdvice, el primero para cualquier tipo de controlador anotado con @Controller (MCV de Spring) y el segundo esta especializado en controladores de tipo Rest (aplica sólo a los controladores anotados como @RestController)
Para que estos componentes puedan funcionar correctamente y como su nombre lo indica, deben poder atrapar excepciones del tipo específico (jerarquía) que necesitemos manejar y hacer la interpretación de la excepciín, como por ejemplo: se requiere atrapar cualquier Exception del tipo IllegalArgumentException y convertirla en un bad request (error 400 de http).
Hablemos un poco de excepciones
Los manejadores de excepciones siempre actúan cuando una excepción es lanzada y no ha sido atrapada por la capa de controladores, éstas pueden ser lanzadas desde las capas de acceso a datos, directamente desde la capa de negocio o simplemente desde la capa de controladores (validación de entrada).
Aquí la discusión se vuelve un poco densa y depende mucho el cómo quieres trabajar tu jerarquía de excepciones, puede ser tan compleja como quieras (casos en que necesitas mucho detalle en la respuesta del manejador) o tan simple como mantener un par de excepciones que manejar e interpretar.
En un articulo anterior escribí acerca de tipos de excepciones, jerarquía y características el manejo de excepciones. Les dejo aquí el enlace a este artículo Checked y Unchecked Exception.
Una de las alternativas mas simples es utilizar Excepciones de tipo RunTimeException ya que tienen la característica de no ser atrapadas de forma imperativa o declarativa, de esta forma excepciones que son lanzadas en las capas inferiores saltan hasta el manejador de Excepciones, claro está, solo si son manejadas en dicho manejador.
Veamos un ejemplo
En SpringBoot tenemos un par de mecanismos para poder manejar excepciones y que nos permite evitar exponer el stack trace directo al usuario (nos da seguridad en caso de exponer algún dato sensible) y en su reemplazo nos permite modelar una salida elegante dependiendo de la excepción lanzada.
Para efectos prácticos en este artículo utilizaremos sólo excepciones de tipo Runtime ya que este tipo de excepciones suben por el stack sin ser capturadas por nuestro código hasta llegar al lugar donde queremos hacer el manejo de la excepción.
Rest Controller Advice
GlobalExceptionHandleres una clase que se anota con @RestControllerAdvice y es donde podremos hacer el manejode la excepción. Veamos un ejemplo a continuación:
En este primer ejemplo, se evidencia que se atrapan las exepciones especificas y mas altas de la jerarquía, NotFoundException, BadRequestException y UnauthorizedException, ademas para cada una de ellas hay un método disponible donde podremos por ejemplo hacer un log del error para tener el detalle de lo ocurrido y finalmente enviar hacia el consumnidor, el correspondiente codigo Http para cada caso. Nota: en este ejemplo el body de la respuesta es vacío.
1 | package cl.pcollaog.eh.handler; |
En este segundo ejemplo, vamos a manipular la respuesta (vamos a suponer que la integración lo pide así) para cada caso.
1 | package cl.pcollaog.eh.handler; |
Para el caso de NotFoundException la respuesta será la siguiente:
1 | 404 |
Para el caso de BadRequestException la respuesta será la siguiente:
1 | 400 |
Finalmente para el caso de UnauthorizedException la respuesta será la siguiente:
1 | 401 |
Como podrán ver, en el handler (método) podrán manejar la excepción a conveniencia y podrán enviar el mensaje y codigo de error que necesiten hacia el consumidor del servicio.
Para finalizar les dejo un diagrama del funcionamiento del GlobalExceptionHandler con el ejemplo de 404 Not Found.
Si tienen alguna observación no duden en comentar.
Manejo de excepciones en Spring Boot
https://blog.pcollaog.cl/2024/03/16/SpringBoot-Exception-Handling/