Ejemplo de manejo centralizado de excepciones en Java Spring
🎯 Objetivo
✔ No usar try-catch en cada controlador
✔ Manejar errores desde un solo lugar (@ControllerAdvice)
✔ Mostrar páginas amigables (Thymeleaf + Bootstrap)
✔ Mantener separación limpia (MVC)
🧩 1. Escenario de ejemplo
Supongamos:
👉 Tienes un módulo de usuarios
👉 Buscas un usuario por ID
👉 Si no existe → lanzar excepción
🧱 2. Excepción personalizada
package com.tuempresa.login.exception;
public class RecursoNoEncontradoException extends RuntimeException {
public RecursoNoEncontradoException(String mensaje) {
super(mensaje);
}
}
🧠 3. Servicio (donde se lanza la excepción)
@Service
public class UsuarioService {
@Autowired
private UsuarioRepository repo;
public Usuario obtenerPorId(Long id) {
return repo.findById(id)
.orElseThrow(() -> new RecursoNoEncontradoException("Usuario no encontrado con ID: " + id));
}
}
🎮 4. Controlador (SIN try-catch)
@Controller
@RequestMapping("/usuarios")
public class UsuarioController {
@Autowired
private UsuarioService service;
@GetMapping("/{id}")
public String verUsuario(@PathVariable Long id, Model model) {
Usuario usuario = service.obtenerPorId(id);
model.addAttribute("usuario", usuario);
return "usuarios/usuario-detalle";
}
}
👉 Nota: aquí NO hay manejo de errores
🌍 5. Manejo centralizado de Excepciones
GlobalExceptionHandler.java
package com.tuempresa.login.exception;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.security.access.AccessDeniedException;
@ControllerAdvice
public class GlobalExceptionHandler {
// 404 - recurso no encontrado
@ExceptionHandler(RecursoNoEncontradoException.class)
public ModelAndView handleNotFound(RecursoNoEncontradoException ex) {
ModelAndView mav = new ModelAndView("error/404");
mav.addObject("mensaje", ex.getMessage());
return mav;
}
// 403 - acceso denegado
@ExceptionHandler(AccessDeniedException.class)
public ModelAndView handleAccessDenied() {
ModelAndView mav = new ModelAndView("error/403");
mav.addObject("mensaje", "No tienes permisos para acceder");
return mav;
}
// 500 - error general
@ExceptionHandler(Exception.class)
public ModelAndView handleGeneral(Exception ex) {
ModelAndView mav = new ModelAndView("error/500");
mav.addObject("mensaje", "Error interno del sistema");
return mav;
}
}
🎨 6. Vistas (Thymeleaf + Bootstrap)
📄 templates/error/404.html
<!DOCTYPE html>
<html>
<head>
<title>No encontrado</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container text-center mt-5">
<h1 class="text-warning">404</h1>
<p th:text="${mensaje}"></p>
<a href="/home" class="btn btn-primary">Volver</a>
</div>
</body>
</html>
📄 templates/error/403.html
<h1>403 - Acceso denegado</h1>
<p th:text="${mensaje}"></p>
📄 templates/error/500.html
<h1>500 - Error interno</h1>
<p th:text="${mensaje}"></p>
🎨 vista usuario-detalle.html
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <title>Detalle de Usuario</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"></head><body class="bg-light">
<div class="container mt-5">
<div class="card shadow"> <div class="card-header bg-primary text-white"> <h4>Detalle de Usuario</h4> </div>
<div class="card-body">
<table class="table table-bordered"> <tr> <th>ID</th> <td th:text="${usuario.id}"></td> </tr>
<tr> <th>Usuario</th> <td th:text="${usuario.username}"></td> </tr>
<tr> <th>Estado</th> <td> <span th:if="${usuario.enabled}" class="badge bg-success">Activo</span> <span th:unless="${usuario.enabled}" class="badge bg-danger">Inactivo</span> </td> </tr>
<tr> <th>Roles</th> <td> <span th:each="rol : ${usuario.roles}" th:text="${rol.nombre}" class="badge bg-info me-1"> </span> </td> </tr> </table>
<div class="mt-3"> <a href="/home" class="btn btn-secondary">Volver</a> </div>
</div>
</div>
</div>
</body></html>
Probar desde un boton x<a th:href="@{/usuarios/1}" class="btn btn-primary">Ver</a>🧠 Flujo completo
Usuario accede a
/usuarios/10❌ No existe →
RecursoNoEncontradoException👉
@ControllerAdvicela captura👉 retorna vista
error/404✔ Usuario ve página bonita
🚀 Resultado final
✔ Código limpio
✔ Sin try-catch repetitivos
✔ Manejo centralizado real
✔ Vistas profesionales
✔ Escalable para APIs o MVC
Comentarios
Publicar un comentario