Saltar al contenido principal

Manejo de errores

Código Completo
El resultado final del código desarrollado en este documento se puede encontrar en el monorepo de GitHub springboot-demo-projects, bajo el tag normalize-errors.

Ahí afuera te vas a encontrar con muchos escenarios como el siguiente:

  • service.com/users/-1 devuelve:

    {
    "errorDescription": "User not found",
    "cause": "BAD REQUEST"
    }
  • pero service.com/product/-1 devuelve:

    {
    "message": "not found",
    "error": 404
    }

La consistencia se fue por la ventana ahí, y se pone peor con errores dentro de 200OK. No queremos ser ese tipo de dev: vamos a hacer un manejo de errores apropiado.

Manejo de Errores

@RestControllerAdvice actúa como un "coordinador de errores" central para tu aplicación.

  • Es un lugar único donde podés definir cómo todos los errores, excepciones, o escenarios inesperados se traducen en respuestas.
  • En lugar de esparcir la lógica de manejo de errores por cada controlador, esta herramienta asegura que cada error, ya sea de una búsqueda de usuario, búsqueda de producto, o bug interno, siga las mismas reglas y formato.

Problem Details for HTTP APIs es una "plantilla de error" estandarizada que estructura respuestas de forma clara y consistente. Pensalo como un formulario pre-diseñado que cada error completa:

  • Qué tipo de error ocurrió (por ejemplo, "film_not_found")
  • Un título legible por humanos (por ejemplo, "Resource Not Found")
  • El código de estado HTTP (por ejemplo, 404)
  • Detalles adicionales (por ejemplo, "Film ID -1 does not exist")

Juntas, estas herramientas aseguran que tu app nunca confunda a los clientes con formatos de error dispares. Incluso casos edge o errores no anticipados se envuelven en la misma estructura predecible.

Archivos a Crear/Modificar
File Tree
├── ...
└── src
├── main
│ ├── java
│ │ └── dev
│ │ └── pollito
│ │ └── spring_java
│ │ ├── config
│ │ │ ├── advice
│ │ │ │ └── ControllerAdvice.java
│ │ │ └── ...
│ │ └── ...
│ └── ...
└── ...

Creá la clase @RestControllerAdvice

java/dev/pollito/spring_java/config/advice/ControllerAdvice.java
package dev.pollito.spring_java.config.advice;

import static io.opentelemetry.api.trace.Span.current;
import static java.time.Instant.*;
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.NOT_FOUND;

import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;

@RestControllerAdvice
@Slf4j
public class ControllerAdvice {
private static @NotNull ProblemDetail buildProblemDetail(
@NotNull Exception e, @NotNull HttpStatus status) {
String exceptionSimpleName = e.getClass().getSimpleName();
String logMessage = "{} being handled";

switch (status.series()) {
case SERVER_ERROR -> log.error(logMessage, exceptionSimpleName, e);
case CLIENT_ERROR -> log.warn(logMessage, exceptionSimpleName, e);
default -> log.info(logMessage, exceptionSimpleName, e);
}

ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(status, e.getLocalizedMessage());
problemDetail.setProperty("timestamp", ISO_INSTANT.format(now()));
problemDetail.setProperty("trace", current().getSpanContext().getTraceId());

return problemDetail;
}

@ExceptionHandler(Exception.class)
public ProblemDetail handle(Exception e) {
return buildProblemDetail(e, INTERNAL_SERVER_ERROR);
}

@ExceptionHandler(NoResourceFoundException.class)
public ProblemDetail handle(NoResourceFoundException e) {
return buildProblemDetail(e, NOT_FOUND);
}
}

Si visitás una uri que no existe (como http://localhost:8080), ahora vas a obtener un error estandarizado:

Terminal
curl -s http://localhost:8080 | jq; curl -sw "→ HTTP %{http_code}\n" -o /dev/null http://localhost:8080
{
"detail": "No static resource for request '/'.",
"instance": "/",
"status": 404,
"title": "Not Found",
"timestamp": "2026-01-11T20:16:13.240960834Z",
"trace": "d9178227-18d6-4442-8598-9a9f17f65f9c"
}
→ HTTP 404

Esto es lo que pasa por detrás:

Scroll to zoom • Drag corner to resize

Handlers Comunes Que Podés Necesitar

Si ocurre una excepción que no es manejada por ningún @ExceptionHandler específico en tu @RestControllerAdvice, va a caer en el @ExceptionHandler(Exception.class) default que retorna una respuesta genérica de 500 Internal Server Error.

Acá están las excepciones más comunes que vas a querer manejar explícitamente:

ExcepciónDescripciónEjemploNotas
ConstraintViolationExceptionParámetros/campos de request fallan validación (@NotNull, @Size, @Pattern)Request body faltando un campo requeridoRequiere Jakarta EE (para agregar después)
MethodArgumentTypeMismatchExceptionParámetro de request no puede ser convertido al tipo esperadoControlador espera Integer pero recibe String
NoResourceFoundExceptionRequest accede a un recurso Spring MVC inexistenteAccediendo a un endpoint indefinido
NoSuchElementExceptionOptional.get() llamado sobre un Optional vacíoBuscando un usuario inexistente por ID
PropertyReferenceExceptionPropiedad inválida usada en query de repositorio Spring DataOrdenando por un campo inexistenteRequiere Spring Data (para agregar después)