SLIDE 1 / 7

1. El Árbol Genealógico de Throwable

Toda anomalía en Java desciende de Throwable. La rama Error representa fallos catastróficos de la JVM (no se atrapan). La rama Exception se divide entre Checked (obligatorias por compilador) y RuntimeException (bugs lógicos unchecked).

Throwable Error (Fatal JVM) OutOfMemoryError StackOverflowError Exception (Recuperables) Aplicación puede responder Checked Exception IOException, SQLException throws o try/catch RuntimeException NullPointerException, Index... Unchecked (Bugs lógicos)
Filtro de Excepción
Categoría: Checked vs Unchecked
Obligación del Compilador: Exige declarar o atrapar
// Checked: Obligación sintáctica
public void leerArchivo() throws IOException {
  new FileInputStream("datos.csv");
}

2. Anatomía de una Excepción en Memoria

Una excepción no es una simple señal de error: es un OBJETO REAL en el Heap. Al crearse con new, captura una instantánea exacta de la pila de llamadas (StackTrace), un mensaje explicativo y opcionalmente una causa original.

Objeto en Heap: SaldoInsuficienteException @0x3E80 String message: "Saldo insuficiente: requerido $500.00, disponible $120.00" StackTraceElement[] stackTrace: at CuentaBancaria.debitar(CuentaBancaria.java:45) at CajeroService.retirar(CajeroService.java:22) Throwable cause: null (Error raíz primario)
Campos de Diagnóstico
Método getMessage(): Explicación para humanos / logs
Costo de new Exception: fillInStackTrace() recorre la pila
Buena Práctica: No usar excepciones para control de flujo
if (saldo < monto) {
  throw new SaldoInsuficienteException(
    "Saldo insuficiente: requerido " + monto
  );
}

3. Desenrollado de la Pila (Stack Unwinding)

Cuando ocurre una excepción y el método no tiene catch, su marco de ejecución colapsa de inmediato y el error se propaga hacia el método que lo llamó, subiendo hasta encontrar un catch o tirar la JVM.

Call Stack (Hilos Activos) main() [try-catch] procesarOrden() validarStock() consultarBD() ⚡ Paso 1: Error en consultarBD() SQLException detonada Sin bloque catch local Acción de la JVM: Destruir frame consultarBD() Propaga a: validarStock()
Simulador de Propagación
Marco Actual: consultarBD()
¿Tiene catch coincidente?: NO -> Frame destruido
try {
  servicio.procesarOrden();
} catch (SQLException e) {
  // main() captura el error y rescata el hilo
  LOGGER.error("Fallo en BD: " + e.getMessage());
}

4. Máquina de Estados: try-catch-finally

El bloque finally se ejecuta SIEMPRE: tanto si el try termina con éxito, como si ocurre una excepción atrapada, o incluso si la excepción no coincide y sigue subiendo por la pila.

Bloque try Código riesgoso Bloque catch Manejo de error Bloque finally ¡SIEMPRE corre! Ruta Activa: Camino Feliz (Sin Excepción) 1. Ejecuta try -> 2. Saltea catch -> 3. Ejecuta finally -> 4. Sigue El bloque finally liberó recursos de forma segura.
Selector de Rutas
Ejecución de finally: Garantizada al 100%
Retorno en try: finally corre antes de salir
try {
  // operacion()
} catch (Exception e) {
  // manejo()
} finally {
  conexion.cerrar(); // Siempre garantizado
}

5. try-with-resources y AutoCloseable

Desde Java 7, cualquier recurso que implemente AutoCloseable se puede abrir en los paréntesis del try. La JVM garantiza llamar a close() automáticamente al salir del bloque, incluso ante excepciones.

✕ Código Legado (Pre-Java 7) Reader r = null; try { r = new Reader(); } finally { if (r != null) { try { r.close(); } catch (IOException e) {} } } // 25 líneas propensas a leaks ✓ try-with-resources (Moderno) try (Reader r = new Reader()) { r.read(); } // close() automático aquí AutoCloseable Limpio, seguro y sin fugas de socket/file
Contrato AutoCloseable
Cierre Implícito: r.close() antes de catch/finally
Excepciones Suprimidas: e.getSuppressed()
// Varios recursos separados por punto y coma
try (var in = new FileInputStream(f1);
     var out = new FileOutputStream(f2)) {
  in.transferTo(out);
}

6. Excepciones Personalizadas y Causas Encadenadas

Traducí errores de infraestructura de bajo nivel a conceptos del dominio usando el constructor con cause: throw new PagoFallidoException("No se pudo cobrar", rootCause). Así se conserva el stack trace original sin exponer detalles crudos al usuario.

PagoFallidoException (Dominio) "Error al cobrar orden ID 1042" cause: SocketTimeoutException → SocketTimeoutException (Infra) "Read timed out at port 443" Causa física original Consola / Logs en Producción: com.tienda.PagoFallidoException: Error al cobrar orden ID 1042 Caused by: java.net.SocketTimeoutException: Read timed out
Buenas Prácticas de Dominio
Constructor con Causa: super(mensaje, causa);
Preservación de Trazas: No se pierde el origen real
public class PagoException extends Exception {
  public PagoException(String msg, Throwable cause) {
    super(msg, cause); // Encadenamiento
  }
}

7. Los 4 Antipatrones a Evitar

Errores que destruyen la observabilidad y causan fallos silenciosos en producción. Identificalos y evitalos en tus proyectos.

1. El Tragador de Errores catch (Exception e) { } Bomba de tiempo: silencia bugs 2. Atrapador Indiscriminado catch (Throwable t) Atrapa OutOfMemoryError sin querer 3. Loguear y Relanzar log.error(e); throw e; Duplica líneas y satura el log 4. Control de Flujo con Exception while(true) { try { ... } } Lento; romper pila es costoso
Regla de Oro
Responsabilidad: Atrapá solo si podés recuperarte
Si no podés recuperarte: Dejá que suba o encapsulá
// O lo manejás de verdad, o lo dejás subir
try {
  debitar();
} catch (SaldoInsuficienteException e) {
  notificarCliente(e.getMessage()); // Acción concreta
}