Jerarquía de excepciones

checked, unchecked y Error

La jerarquía

La distinción checked/unchecked es específica de Java — no existe en la mayoría de lenguajes. Las checked exceptions son parte del contrato de un método: el compilador obliga a que el llamador las maneje o declare. Las unchecked (RuntimeException) no — se usan para indicar bugs en el código.

// Jerarquía simplificada:
//
// Throwable
// ├── Error                    ← problemas graves de la JVM — no capturar
// │   ├── OutOfMemoryError
// │   ├── StackOverflowError
// │   └── AssertionError
// └── Exception
//     ├── IOException          ← checked: el compilador obliga a manejarla
//     │   ├── FileNotFoundException
//     │   └── SocketException
//     ├── SQLException         ← checked
//     └── RuntimeException    ← unchecked: no obliga al compilador
//         ├── NullPointerException
//         ├── IllegalArgumentException
//         ├── IllegalStateException
//         ├── IndexOutOfBoundsException
//         └── UnsupportedOperationException

Checked exceptions

Se usan para condiciones recuperables externas al programa: archivo no encontrado, conexión caída, permiso denegado. El llamador puede tomar una acción específica: reintentar, usar un fichero alternativo, mostrar un mensaje al usuario. Cuando no hay acción recuperable, lo habitual es envolver la checked en una RuntimeException.

// Checked: el compilador obliga a declarar o capturar la excepción

// Opción 1: capturar con try-catch
public String leerFichero(String ruta) {
    try {
        return new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(ruta)));
    } catch (IOException e) {
        throw new RuntimeException("No se pudo leer " + ruta, e);
    }
}

// Opción 2: declarar con throws (delegar al llamador)
public String leerFichero2(String ruta) throws IOException {
    return new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(ruta)));
}

Unchecked exceptions (RuntimeException)

Indican bugs en el código: precondiciones violadas, estado inválido, índices fuera de rango. El llamador no puede "recuperarse" de ellos porque son un error de programación que debe corregirse, no una condición esperada en tiempo de ejecución.

// Unchecked (RuntimeException): no obliga al compilador a manejarla
// Se usan para bugs de programación (precondiciones, estado inválido)

public class Pila<T> {
    private final Object[] datos;
    private int tope = 0;

    public Pila(int capacidad) {
        if (capacidad <= 0)
            throw new IllegalArgumentException("Capacidad debe ser positiva"); // unchecked
        datos = new Object[capacidad];
    }

    @SuppressWarnings("unchecked")
    public T pop() {
        if (tope == 0)
            throw new IllegalStateException("Pila vacía"); // unchecked
        return (T) datos[--tope];
    }
}

Error

Los Error indican condiciones anómalas graves de la JVM o entorno de ejecución. La regla general es no capturarlos: si la JVM se queda sin memoria o la pila de llamadas se desborda, el estado del programa es inconsistente y no tiene sentido continuar.

// Error: problemas graves de la JVM — generalmente NO capturar
// La JVM no puede continuar de forma fiable después de un Error

try {
    // código
} catch (StackOverflowError e) {
    // Solo en casos muy específicos (ej: proteger un servidor de peticiones maliciosas)
    // En la mayoría de casos: NO capturar — dejar que la JVM termine
}

// OutOfMemoryError:
// Puede capturarse pero raramente tiene sentido — la JVM está en estado inconsistente
// Si ocurre frecuentemente, ajustar -Xmx o buscar memory leaks
Debate industria: hay opiniones divididas sobre el uso de checked exceptions. Spring, Hibernate y la mayoría de frameworks modernos prefieren unchecked exceptions para no contaminar las firmas de los métodos con throws. La tendencia en Java moderno es usar checked solo cuando el llamador puede tomar una acción recuperable específica.

Siguiente apartado → Lanzar excepciones: throw, throws y diseño de contratos

Índice de la sección

Índice del curso