Skip to content
Menu
Laboratorios TERA Byte
  • Home
  • Quienes Somos
  • Política de privacidad
  • Contáctame
Laboratorios TERA Byte

Errores en Java: Más Allá del try-catch ⚡️

Posted on 12 August, 202512 August, 2025
  • 1) Filosofía: ¿Error, Excepción o Resultado?
  • 2) Checked vs Unchecked: decide con intención
  • 3) Jerarquía de excepciones de dominio (limpia y expresiva)
  • 4) Errores vs Validación: no todo merece una excepción
  • 5) Anti-patrones que matan la mantenibilidad
  • 6) Fronteras limpias: mapping por capas
  • Conclusión

En Java es fácil caer en el “try { ... } catch (Exception e) { ... } y ya” — pero construir sistemas robustos exige una estrategia clara: qué lanzar, dónde capturar, cómo reportar y cómo recuperarse. Aquí tienes una guía práctica, desde buenas prácticas del lenguaje hasta patrones de backend productivo con Spring.


1) Filosofía: ¿Error, Excepción o Resultado?

  • Excepciones: eventos excepcionales que invalidan el flujo normal.
  • Resultados explícitos: cuando el fallo es esperable (ej. validación).
  • Logs/Métricas: convierten errores en observabilidad (SRE mindset).

Regla de oro:

  • Errores de programación (NPE, index out of bounds) → unchecked.
  • Fallos recuperables de negocio (validación, insuficiencia de saldo) → resultado explícito o excepción de dominio unchecked, capturada cerca de la frontera (API/servicio).
  • Fallos de infraestructura (timeout, red, DB down) → excepción unchecked + reintentos/backoff + telemetría.

2) Checked vs Unchecked: decide con intención

  • Checked (IOException) obligan a manejar/propagar; tienden a “ensuciar” firmas.
  • Unchecked (RuntimeException) simplifican APIs y son estándar en frameworks modernos.

Recomendación práctica (Java 17+):

  • Usa unchecked para casi todo en aplicaciones (dominio e infraestructura).
  • Reserva checked para librerías o integraciones donde el contrato requiera fuerza en manejo.

3) Jerarquía de excepciones de dominio (limpia y expresiva)

Diseña excepciones con intención y metadatos.

Java
public abstract class DomainException extends RuntimeException {
    private final String code;
    private final Map<String, Object> details;

    protected DomainException(String code, String message, Map<String, Object> details, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.details = details == null ? Map.of() : Map.copyOf(details);
    }
    public String code()    { return code; }
    public Map<String, Object> details() { return details; }
}

public final class InsufficientFundsException extends DomainException {
    public InsufficientFundsException(String accountId, BigDecimal balance, BigDecimal amount) {
        super(
          "INSUFFICIENT_FUNDS",
          "Saldo insuficiente para debitar " + amount,
          Map.of("accountId", accountId, "balance", balance, "amount", amount),
          null
        );
    }
}

Ventajas:

  • Código legible en logs/JSON (code, details).
  • Mapeo claro a HTTP (400/409) o a eventos de error

4) Errores vs Validación: no todo merece una excepción

Para validaciones esperables, prefiere resultados explícitos que no rompen el flujo.

  • Optional para presencia/ausencia (no para errores detallados)
Java
Optional<User> user = userRepo.findByEmail(email);
if (user.isEmpty()) return Result.fail("USER_NOT_FOUND");
  • Result<T,E> con sealed (Java 17)
Java
public sealed interface Result<T, E> permits Result.Ok, Result.Err {
    record Ok<T, E>(T value) implements Result<T, E> {}
    record Err<T, E>(E error) implements Result<T, E> {}

    static <T,E> Ok<T,E> ok(T v){ return new Ok<>(v); }
    static <T,E> Err<T,E> err(E e){ return new Err<>(e); }
}

// USO

Result<Order, String> r = service.placeOrder(cmd);
if (r instanceof Result.Err<Order, String> e) {
    return Response.badRequest(e.error()); // sin excepciones para flujos esperables
}

5) Anti-patrones que matan la mantenibilidad

  • Cachar Exception genérico y tragarlo:
Java
try { ... } catch (Exception e) { /* nada */ } // ❌
  • Perder el cause al relanzar
Java
throw new RuntimeException("falló"); // ❌ sin cause
// ✅
throw new RuntimeException("falló", e);
  • Excepciones para control de flujo normal (ej. usar try/catch para buscar en un mapa).
  • Logear tres veces el mismo error (controller, service, filter) → ruido.
  • Mensajes crípticos sin contexto (IDs, inputs sanitizados)

6) Fronteras limpias: mapping por capas

  • Dominio/Aplicación: lanza DomainException y no logees aquí (deja el log a la frontera).
  • Infraestructura: encapsula excepciones de proveedor (DB/HTTP) a las tuyas.
  • Frontera API (controller/filter): mapea a HTTP/JSON y logea una sola vez

Spring Boot: @ControllerAdvice + Problem Details (RFC 7807 style)

Java
@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(DomainException.class)
    public ResponseEntity<ProblemDetail> handleDomain(DomainException ex) {
        ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
        pd.setTitle(ex.code());
        pd.setDetail(ex.getMessage());
        ex.details().forEach(pd::setProperty);
        return ResponseEntity.of(pd).build();
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ProblemDetail> handleUnexpected(Exception ex) {
        ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR);
        pd.setTitle("UNEXPECTED_ERROR");
        pd.setDetail("Ocurrió un error inesperado. Intenta más tarde.");
        // log único con stacktrace + correlationId
        return ResponseEntity.of(pd).build();
    }
}

Conclusión

El manejo de errores en Java no se trata solo de envolver código en un try-catch y cruzar los dedos.
Cuando defines qué lanzar, dónde capturarlo y cómo reportarlo, tu código deja de ser reactivo y empieza a ser intencional:

  • Usas excepciones con propósito, no por inercia.
  • Separas errores esperables de los inesperados.
  • Centralizas la lógica de manejo para que tu aplicación sea predecible y mantenible.
  • Das a tus consumidores (APIs, servicios internos o externos) respuestas claras y consistentes.

En pocas palabras: pasas de “apagar fuegos” a diseñar resiliencia en tu software.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Comentarios recientes

No comments to show.

Archivos

  • August 2025
  • June 2025

Categorías

  • Blog
©2025 Laboratorios TERA Byte | Powered by SuperbThemes!