Cómo Gestionar Excepciones en Spring Boot con Anotaciones

Cuando trabajamos con Spring Framework, uno de los pilares fundamentales para crear APIs robustas (y fáciles de mantener) es el control de excepciones. No se trata únicamente de capturar errores, sino de gestionarlos de forma ordenada, centralizada y, sobre todo, comprensible para quien consume nuestra aplicación. En este post te vamos a guiar paso a paso para dominar el manejo de excepciones en Spring, desde los controladores individuales hasta la gestión global con anotaciones, incluyendo cómo definir tus propias excepciones personalizadas.

 

 

¿Qué es el control de excepciones en Spring?

 

En una aplicación Java típica, cuando se lanza una excepción no capturada, se pueden producir errores no deseados o respuestas poco útiles para el cliente. Spring ofrece un mecanismo elegante para interceptar y tratar esas excepciones, devolviendo respuestas claras y personalizadas según el contexto.

 

La idea es sencilla: capturamos excepciones específicas o genéricas y devolvemos una respuesta adecuada (por ejemplo, un JSON con el código de error y un mensaje significativo), sin necesidad de repetir esa lógica en cada clase controladora.

 

 

🔄 @ExceptionHandler: control puntual de errores

 

La anotación @ExceptionHandler permite gestionar excepciones dentro de un controlador específico. Se asocia a un método que recibe la excepción y devuelve una respuesta personalizada. Se debe crear un ExceptionHandler por cada tipo de Exception que se quiere capturar (NoSuchElementException, RunTimeException, etc…)

 

🔎 Ejemplo de uso de @ExceptionHandler


 
@RestController
@RequestMapping("/empresas")
public class EmpresaController {

    @GetMapping("/{id}")
    public Empresa obtenerEmpresa(@PathVariable String id) {
        if (id.equals("999")) {
            throw new NoSuchElementException("Empresa no encontrada");
        }
        return new Empresa(id, "ACME Corp");
    }

    @ExceptionHandler(NoSuchElementException.class)
    public ResponseEntity<Map<String, String>> handleNotFound(NoSuchElementException ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }
}


Aquí, si se lanza una "NoSuchElementException", el método anotado con @ExceptionHandler se activa y devuelve una respuesta controlada.

 

🔄 @ControllerAdvice: gestión global para controladores

 

ControllerAdvice es una anotación que permite definir una clase separada para centralizar el control de excepciones de todos los controladores que devuelven vistas (HTML o Thymeleaf, por ejemplo).

 

🔎 Ejemplo de ControllerAdvice

 

@ControllerAdvice
public class ControladorErrores {

    @ExceptionHandler(NullPointerException.class)
    public String gestionarNullPointer(Model model, Exception ex) {
        model.addAttribute("mensaje", ex.getMessage());
        return "error"; // Nombre de la vista HTML
    }
}

  

Este controlador global captura todos los "NullPointerException" lanzados desde cualquier controlador tradicional y devuelve una vista personalizada de error.

 

 

🔄 @RestControllerAdvice: para APIs REST

 

Cuando trabajamos con microservicios o con APIs REST, lo más recomendable es usar @RestControllerAdvice, una variante que combina @ControllerAdvice con @ResponseBody. Esto significa que la respuesta se serializa automáticamente a JSON, ideal para APIs.

 

🔎 Ejemplo de RestControllerAdvice

 

@RestControllerAdvice
public class GestorGlobalExcepciones {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Map<String, String>> gestionarArgumentoInvalido(IllegalArgumentException ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", "Petición inválida: " + ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }
}

 

Este gestor global se activa para cualquier excepción "IllegalArgumentException" lanzada desde cualquier controlador @RestController.

 

 

🔄 Excepciones personalizadas: control a medida

 

Más allá de las excepciones estándar de Java, muchas veces necesitamos lanzar errores propios de nuestra lógica de negocio. Para ello, Spring nos permite definir nuestras propias excepciones y gestionarlas centralizadamente.

 

Paso 1: Crear la clase de excepción personalizada

 

En primer lugar, nos creamos la Exception personalizada que vamos a querer capturar en nuestra aplicación. Por ejemplo, una “EmpresaServiceException” para cuando falle nuestro servicio EmpresaService.

 
 
public class EmpresaServiceException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public EmpresaServiceException(String mensaje) {
        super("EmpresaServiceException: " + mensaje);
    }
}
 
 
 
 

Paso 2: Crear el modelo de error que se devolverá al cliente

 
A continuación, tenemos que crearnos la entidad Excepción que queremos devolver cuando se produzca el error en algún lugar de nuestra aplicación.
 

public class ErrorMessage {

    private Date timestamp;
    private String message;

    public ErrorMessage(Date timestamp, String message) {
        this.timestamp = timestamp;
        this.message = message;
    }

    // Getters y setters
}

 
 
 
 
 

Paso 3: Definir un RestControllerAdvice que capture esta excepción

 

Nos creamos el gestor de Excepciones que es como el Controlador que va a ir capturando las excepciones tipo personalizadas que se produzcan en nuestra aplicación.

👉 Por ejemplo, este ControllerAdvice va a capturar las excepciones genéricas y las excepciones personalizadas "EmpresaServiceException".

 


@RestControllerAdvice
public class GestorErroresGlobales {

    @ExceptionHandler(EmpresaServiceException.class)
    public ResponseEntity<ErrorMessage> gestionarErrorServicio(EmpresaServiceException ex) {
        ErrorMessage error = new ErrorMessage(new Date(), ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorMessage> gestionarErroresGenericos(Exception ex) {
        ErrorMessage error = new ErrorMessage(new Date(), ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

 
 
 

Paso 4: Lanzar la excepción personalizada desde tu código

 

Finalmente, podremos ir devolviendo la excepción personalizada en todos los puntos de nuestra aplicación que lo precisen.

Por ejemplo, devolvemos:

 

@RestController
@RequestMapping("/api/empresas")
public class EmpresaRestController {

    @GetMapping("/{id}")
    public Empresa getEmpresa(@PathVariable String id) {
        if (id.equals("0")) {
            throw new EmpresaServiceException("ID de empresa inválido");
        }
        return new Empresa(id, "Globex Corporation");
    }
}

 
 
 

Resultado esperado: respuesta JSON cuando se produce un error

 

Si se produce ese tipo de excepción, con la configuración anterior nuestra aplicación recibirá una respuesta errónea personalizada.

La invocación al controlador anterior se hará del siguiente modo:

http://localhost:8080/api/empresas/5045878

 

❌ Si se produce un error se recibirá este JSON.  

{ "timestamp": "2025-05-07T10:43:00.123+00:00", "message": "EmpresaServiceException: ID de empresa inválido" }



Conclusión

 

Gestionar excepciones correctamente en Spring no sólo mejora la experiencia del usuario, sino que además blinda tu aplicación ante fallos y facilita su mantenimiento. Con las anotaciones Spring de excepciones puedes centralizar toda la lógica de errores de manera limpia y eficaz.

 

Además, con las excepciones personalizadas puedes reflejar la lógica de negocio de forma más precisa, permitiendo que los errores tengan un significado claro para quien consume tu API o aplicación web.

 

 ¡Nos vemos en el siguiente post!

Saludos.

 

Comentarios

Entradas populares de este blog

Componentes y Ventanas de Java Swing

Configurar Apache Tomcat en Eclipse

Creación de Webservice SOAP mediante Anotaciones