En el desarrollo con Spring Boot, un error común que puede confundir a los desarrolladores es la excepción org.springframework.web.servlet.DispatcherServlet - Handler dispatch failed. Esta ocurre cuando el DispatcherServlet ha invocado con éxito un controlador (handler), pero algo falla durante el proceso de preparación o envío de la respuesta HTTP.
Escenario Simple que Provoca la Excepción
La forma más directa de desencadenar este error es provocar una excepción no controlada dentro de un método de un Controller durante el procesamiento de una solicitud. Spring intentará convertir esta excepción en una respuesta de error, pero si no se gestiona adecuadamente, el flujo de despacho (dispatch) se interrumpe.
Código que falla:
@RestController
@RequestMapping("/api/datos")
public class ProcesadorDatosController {
@GetMapping("/consultar")
public ResponseEntity<String> obtenerDato() {
// Lógica de negocio que falla inesperadamente
boolean condicionCritica = false;
if (!condicionCritica) {
throw new IllegalStateException("Error crítico en la lógica de procesamiento");
}
return ResponseEntity.ok("Dato procesado exitosamente");
}
}
Al hacer una petición GET a /api/datos/consultar, el controlador lanza una IllegalStateException. Sin un manejador adecuado, esto se traduce en un fallo en la distribución (dispatch) del handler.
Causas Principales del Fallo
La raíz del problema puede atribuirse a varios factores comunes:
- Excepciones no capturadas: Como en el ejemplo anterior, cualquier excepción de tiempo de ejecución no tratada dentro del controlador o de sus dependencias.
- Fallos en la resolución de vistas: Si el controlador devuelve el nombre de una vista (plantilla) que no existe o no se puede resolver (ej. un archivo JSP, Thymeleaf faltante).
- Problemas de serialización: Errores al intentar convertir el objeto de respuesta (ej. un POJO) a JSON o XML, como por ejemplo una circularidad de referencias o un tipo de dato no compatible.
- Excepciones de E/S: Errores de Entrada/Salida que ocurren durante la escritura de la respuesta en el flujo de salida del
HttpServletResponse.
Estrategias de Corrección
Para resolver y prevenir este error, se deben implementar prácticas de manejo de excepciones robustas.
1. Manejo Local con Try-Catch
La solución inmediata es encapsular el código propenso a fallar dentro de un bloque try-catch y devolver una respuesta de error HTTP adecuada.
Código corregido:
@RestController
@RequestMapping("/api/datos")
public class ProcesadorDatosController {
private static final Logger log = LoggerFactory.getLogger(ProcesadorDatosController.class);
@GetMapping("/consultar")
public ResponseEntity<?> obtenerDato() {
try {
// Lógica de negocio que puede fallar
String resultado = servicioProcesamiento.ejecutarConsulta();
return ResponseEntity.ok(resultado);
} catch (IllegalStateException ex) {
log.error("Fallo en la lógica de procesamiento: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Fallo interno del servidor", "detalle", ex.getMessage()));
} catch (Exception ex) {
log.error("Error inesperado al procesar la solicitud", ex);
return ResponseEntity.badRequest().body(Map.of("error", "Solicitud incorrecta"));
}
}
}
2. Manejo Global con @ControllerAdvice
Una solución más elegante y centralizada es utilizar una clase anotada con @ControllerAdvice para interceptar excepciones de múltiples controladores.
@ControllerAdvice
public class ManejadorExcepcionesGlobal {
private static final Logger log = LoggerFactory.getLogger(ManejadorExcepcionesGlobal.class);
@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<Map<String, String>> manejarExcepcionLogica(IllegalStateException ex) {
log.error("Excepción de lógica de negocio: {}", ex.getMessage());
Map<String, String> cuerpoRespuesta = Map.of(
"tipo", "ERROR_LOGICO",
"mensaje", "La operación no pudo completarse debido a un error interno."
);
return new ResponseEntity<>(cuerpoRespuesta, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> manejarExcepcionGeneral(Exception ex) {
log.error("Error no controlado: ", ex);
Map<String, String> cuerpoRespuesta = Map.of(
"tipo", "ERROR_INESPERADO",
"mensaje", "Ha ocurrido un error inesperado. Por favor, contacte al soporte."
);
return new ResponseEntity<>(cuerpoRespuesta, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Con este manejador global, el controlador puede permanecer limpio de lógica de manejo de excepciones, que queda concentrada en un solo lugar.
3. Verificación de Vistas y Serialización
Si el error persiste y está relacionado con la vista:
- Confirme que el prefijo y sufijo de resolución de vistas están configurados correctamente en
application.properties(ej.spring.mvc.view.prefix=/WEB-INF/views/,spring.mvc.view.suffix=.jsp). - Verifique que el archivo de plantilla exista en la ruta física correspondiente.
- Para aplicaciones REST, asegúrese de que las dependencias de serialización JSON (como Jackson) estén presentes en el classpath y que los objetos de respuesta sean seiralizables.
Prevención a Futuro
Para minimizar estos errores en producción, considere:
- Defensa en profundidad: Implementar tanto
@ControllerAdvicecomo bloquestry-catchen controladores críticos. - Pruebas exhaustivas: Escribir pruebas unitarias y de integración que simulen escenarios de fallo.
- Logging estructurado: Utilizar un framework como SLF4J con Logback para registrar trazas detalladas de los errores, facilitando el diagnóstico.
- Validación de entrada: Usar anotaciones como
@Validy@Validatedpara capturar datos de entrada no válidos antes de que lleguen a la lógica de negocio.
Un enfoque sistemático en el manejo de excepciones transforma este tipo de errores en problemas diagnósticos y resolubles, mejorando la resiliencia de la aplicación Spring Boot.