CodeGym /Cursos /JAVA 25 SELF /Encadenamiento de excepciones (Exception Chaining)

Encadenamiento de excepciones (Exception Chaining)

JAVA 25 SELF
Nivel 24, Lección 2
Disponible

1. Introducción

En esta lección analizaremos una técnica importante para trabajar con excepciones — el encadenamiento de excepciones (exception chaining). Esta técnica permite no perder información sobre la causa original del error, incluso cuando «envuelves» una excepción dentro de otra.

En aplicaciones reales, a menudo sucede que un error aparece en lo profundo de la pila de llamadas — por ejemplo, al trabajar con una base de datos, el sistema de archivos o la red. Supongamos que tienes un método que accede a la base de datos y puede lanzar SQLException. Pero a nivel de lógica de negocio no quieres «ensuciar» el código con detalles técnicos y prefieres lanzar tu propia excepción, por ejemplo, UserManagementException.

¿Qué pasa si simplemente lanzas una nueva excepción?

try {
    // algo con la base de datos
} catch (SQLException e) {
    throw new UserManagementException("Error al trabajar con usuarios");
}

Problema:
En ese caso, la información sobre lo que exactamente ocurrió en la base de datos (¡y la pila de llamadas!) se pierde. En el log solo verás UserManagementException, y cuál fue la causa — desconocido.

2. Solución: envolver la excepción original (chaining)

Java permite «envolver» una excepción dentro de otra, pasando la excepción original como causa (cause) al constructor de la nueva excepción. A esto se le llama encadenamiento de excepciones.

¿Cómo hacerlo?

La mayoría de las excepciones estándar y personalizadas tienen un constructor que acepta un segundo parámetro — Throwable cause:

public UserManagementException(String message, Throwable cause) {
    super(message, cause);
}

Uso:

try {
    // algo con la base de datos
} catch (SQLException e) {
    throw new UserManagementException("Error al trabajar con usuarios", e);
}

Ahora, si miras la pila de llamadas (printStackTrace()), verás tu excepción y toda la cadena hasta la causa original.

3. Cómo obtener la causa de una excepción

Cualquier objeto de tipo Throwable tiene el método getCause(), que devuelve la excepción original (o null si no existe).

Ejemplo:

try {
    // ...
} catch (UserManagementException e) {
    Throwable cause = e.getCause();
    if (cause != null) {
        System.out.println("Causa raíz: " + cause);
    }
    e.printStackTrace();
}

¿Para qué sirve?

  • Para depuración: ves no solo «qué salió mal» en el nivel superior, sino también dónde ocurrió exactamente el error en lo profundo de la pila.
  • Para el registro en logs: puedes escribir en el log toda la cadena de errores.
  • Para transferir información entre capas de la aplicación: la capa de negocio puede «envolver» la excepción técnica en la suya sin perder detalles.

4. Ejemplo: encadenamiento de excepciones en una aplicación real

Supongamos que tienes un método que carga un usuario desde la base de datos:

public User loadUser(String username) throws UserManagementException {
    try {
        // Código que puede lanzar SQLException
        // ...
    } catch (SQLException e) {
        throw new UserManagementException("No se pudo cargar al usuario: " + username, e);
    }
}

Donde UserManagementException es tu propia excepción:

public class UserManagementException extends Exception {
    public UserManagementException(String message, Throwable cause) {
        super(message, cause);
    }
}

¿Qué ocurrirá en caso de error?

  • En los logs se verán tanto tu excepción como el SQLException original con todos los detalles.
  • Si es necesario, puedes acceder a la causa original mediante getCause().

5. Cómo se ve la pila de llamadas con encadenamiento de excepciones

Ejemplo de salida:

UserManagementException: No se pudo cargar al usuario: example_user
    at UserService.loadUser(UserService.java:15)
    ...
Caused by: java.sql.SQLException: Connection refused
    at ...

Aquí se ve todo: la cadena completa de llamadas, dónde surgió el error de negocio y qué excepción técnica fue la causa.

6. Práctica: implementemos el encadenamiento de excepciones

Paso 1. Creamos nuestra propia excepción:

public class UserManagementException extends Exception {
    public UserManagementException(String message) {
        super(message);
    }
    public UserManagementException(String message, Throwable cause) {
        super(message, cause);
    }
}

Paso 2. Usamos el encadenamiento:

try {
    // algo peligroso
} catch (SQLException e) {
    throw new UserManagementException("Error al trabajar con la BD", e);
}

Paso 3. Manejo en el nivel superior:
En el nivel más alto del programa capturamos nuestra propia excepción y mostramos el mensaje de error junto con toda la cadena de causas.

public class Main {
    public static void main(String[] args) {
        try {
            runUserManagement();
        } catch (UserManagementException e) {
            System.err.println("Se produjo un error: " + e.getMessage());
            // Imprimimos la cadena de causas
            Throwable cause = e.getCause();
            while (cause != null) {
                System.err.println("Causa: " + cause.getMessage());
                cause = cause.getCause();
            }
        }
    }

    private static void runUserManagement() throws UserManagementException {
        try {
            // simulación de error de BD
            throw new SQLException("No hay conexión con la BD");
        } catch (SQLException e) {
            throw new UserManagementException("Error al trabajar con la BD", e);
        }
    }
}

7. Errores típicos al trabajar con cadenas de excepciones

Error n.º 1: lanzas una nueva excepción sin la causa.

catch (SQLException e) {
    throw new UserManagementException("Error", /* ¡sin cause! */);
}

Malo: se pierde la información sobre la causa original.

Error n.º 2: no implementaste un constructor con cause en tu propia excepción.
Si tu clase de excepción no tiene un constructor con Throwable cause, no podrás pasar la causa — tendrás que añadirlo manualmente.

Error n.º 3: capturas y silencias la excepción sin propagarla.

catch (SQLException e) {
    // Solo registramos y callamos
}

Malo: el error «se pierde» y el programa sigue funcionando de forma incorrecta.

try {
    userService.loadUser("example_user");
} catch (UserManagementException e) {
    System.err.println("Error: " + e.getMessage());
    if (e.getCause() != null) {
        System.err.println("Causa raíz: " + e.getCause());
    }
    e.printStackTrace();
}
1
Tarea
JAVA 25 SELF, nivel 24, lección 2
Bloqueada
Detective de datos: Identificación de la causa raíz del fallo
Detective de datos: Identificación de la causa raíz del fallo
1
Tarea
JAVA 25 SELF, nivel 24, lección 2
Bloqueada
Ruta del error: Cadena de fallos en el sistema de informes
Ruta del error: Cadena de fallos en el sistema de informes
1
Tarea
JAVA 25 SELF, nivel 24, lección 2
Bloqueada
"Error externo" con "causa": Investigación de las causas
"Error externo" con "causa": Investigación de las causas
1
Tarea
JAVA 25 SELF, nivel 24, lección 2
Bloqueada
Catástrofe espacial: cadena de fallos multinivel
Catástrofe espacial: cadena de fallos multinivel
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION