Volver a Labs
Intermedio

Manejo de Excepciones en Java

1–2 horas Actualizado Junio 2026
Java Excepciones try-catch Backend

Introducción

En un programa real, las cosas salen mal: el usuario ingresa letras donde se esperan números, el archivo no existe, la conexión a la base de datos falla. Si no manejas estos casos, tu programa termina abruptamente con un mensaje de error confuso.

En Java, los errores en tiempo de ejecución se representan como excepciones — objetos que describen qué salió mal y dónde. El lenguaje te da herramientas para detectarlos, manejarlos y continuar la ejecución de forma controlada.

¿Qué vas a aprender en este lab?

  • Entender qué es una excepción y cómo fluye
  • Capturar excepciones con try-catch-finally
  • Lanzar excepciones con throw
  • Crear tus propias excepciones personalizadas
  • Distinguir entre excepciones verificadas y no verificadas
  • Usar try-with-resources para cerrar recursos automáticamente

Requisitos previos: fundamentos de Java y nociones básicas de POO (clases, herencia).


1. ¿Qué es una excepción?

Cuando Java encuentra una operación imposible (dividir por cero, acceder a un índice inválido, convertir texto en número...), lanza un objeto de tipo Exception. Si nadie lo captura, el programa termina y muestra un stack trace — la ruta de llamadas que llevó al error.

int[] numeros = {1, 2, 3};
System.out.println(numeros[5]); // ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
    at Main.main(Main.java:3)

Jerarquía de excepciones

Throwable
├── Error              (problemas graves de la JVM — no se capturan normalmente)
│   ├── OutOfMemoryError
│   └── StackOverflowError
└── Exception
    ├── RuntimeException     (no verificadas — no obligan a try-catch)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── ClassCastException
    │   └── IllegalArgumentException
    └── IOException          (verificadas — el compilador obliga a manejarlas)
        ├── FileNotFoundException
        └── SQLException

2. try-catch

Envuelve el código que puede fallar en un bloque try. Si lanza una excepción, el flujo salta al bloque catch correspondiente.

try {
    int resultado = 10 / 0; // lanza ArithmeticException
    System.out.println(resultado); // esto NO se ejecuta
} catch (ArithmeticException e) {
    System.out.println("Error: " + e.getMessage()); // Error: / by zero
}

System.out.println("El programa continúa."); // esto SÍ se ejecuta

Capturar múltiples excepciones

String[] datos = {"42", "hola", null};

for (String dato : datos) {
    try {
        int numero = Integer.parseInt(dato);
        System.out.println("Número: " + numero);
    } catch (NumberFormatException e) {
        System.out.println("'" + dato + "' no es un número válido.");
    } catch (NullPointerException e) {
        System.out.println("El dato es null.");
    }
}
// Número: 42
// 'hola' no es un número válido.
// El dato es null.

Multi-catch (Java 7+)

Cuando varios tipos de excepción requieren el mismo manejo:

try {
    // código que puede lanzar varias excepciones
} catch (NumberFormatException | IllegalArgumentException e) {
    System.out.println("Dato inválido: " + e.getMessage());
}

Capturar la clase base

try {
    // algún código
} catch (Exception e) {
    // captura CUALQUIER excepción que herede de Exception
    System.out.println("Error inesperado: " + e.getMessage());
}

Capturar Exception directamente es un último recurso. Siempre que sea posible, captura el tipo específico para poder dar una respuesta adecuada.


3. finally

El bloque finally se ejecuta siempre, ocurra o no una excepción. Es el lugar indicado para limpiar recursos (cerrar conexiones, archivos, etc.).

public static int dividir(int a, int b) {
    try {
        return a / b;
    } catch (ArithmeticException e) {
        System.out.println("División por cero.");
        return -1;
    } finally {
        System.out.println("Bloque finally ejecutado."); // siempre se ejecuta
    }
}

System.out.println(dividir(10, 2));  // Bloque finally ejecutado. → 5
System.out.println(dividir(10, 0));  // División por cero. Bloque finally ejecutado. → -1

4. throw — lanzar excepciones

Puedes lanzar una excepción manualmente con throw cuando detectas un estado inválido en tu lógica de negocio.

public static double calcularRaiz(double numero) {
    if (numero < 0) {
        throw new IllegalArgumentException("No se puede calcular la raíz de un número negativo: " + numero);
    }
    return Math.sqrt(numero);
}

// En main:
System.out.println(calcularRaiz(16.0));  // 4.0
System.out.println(calcularRaiz(-4.0));
// Exception in thread "main" java.lang.IllegalArgumentException: No se puede calcular la raíz de un número negativo: -4.0

Capturando la excepción lanzada

try {
    double resultado = calcularRaiz(-9.0);
} catch (IllegalArgumentException e) {
    System.out.println("Error de entrada: " + e.getMessage());
}
// Error de entrada: No se puede calcular la raíz de un número negativo: -9.0

5. Excepciones personalizadas

Crear tus propias excepciones hace que el código sea más descriptivo y permite distinguir errores de tu dominio de errores genéricos de Java.

// Excepción no verificada (extiende RuntimeException)
public class SaldoInsuficienteException extends RuntimeException {
    private final double montoFaltante;

    public SaldoInsuficienteException(double montoFaltante) {
        super("Saldo insuficiente. Faltan: $" + montoFaltante);
        this.montoFaltante = montoFaltante;
    }

    public double getMontoFaltante() {
        return montoFaltante;
    }
}
public class CuentaBancaria {
    private double saldo;

    public CuentaBancaria(double saldoInicial) {
        this.saldo = saldoInicial;
    }

    public void retirar(double monto) {
        if (monto > saldo) {
            throw new SaldoInsuficienteException(monto - saldo);
        }
        saldo -= monto;
    }

    public double getSaldo() { return saldo; }
}
CuentaBancaria cuenta = new CuentaBancaria(100.0);

try {
    cuenta.retirar(150.0);
} catch (SaldoInsuficienteException e) {
    System.out.println(e.getMessage());           // Saldo insuficiente. Faltan: $50.0
    System.out.println("Falta: $" + e.getMontoFaltante()); // Falta: $50.0
}

Excepciones verificadas vs no verificadas

Verificadas (Exception) No verificadas (RuntimeException)
El compilador obliga a manejarlas No
Cuándo usarlas Errores recuperables esperados (archivo no encontrado, red caída) Errores de programación (índice inválido, argumento nulo)
Ejemplo IOException, SQLException NullPointerException, IllegalArgumentException

6. throws — declarar que un método puede fallar

Cuando un método puede lanzar una excepción verificada sin capturarla internamente, debe declararlo con throws:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

// El compilador sabe que este método puede lanzar IOException
public static String leerArchivo(String ruta) throws IOException {
    return Files.readString(Path.of(ruta));
}

// Quien llame al método debe manejarlo
public static void main(String[] args) {
    try {
        String contenido = leerArchivo("datos.txt");
        System.out.println(contenido);
    } catch (IOException e) {
        System.out.println("No se pudo leer el archivo: " + e.getMessage());
    }
}

7. try-with-resources

Cuando trabajas con recursos que deben cerrarse (archivos, conexiones a base de datos, streams), usa try-with-resources. El recurso se cierra automáticamente al terminar el bloque, incluso si ocurre una excepción.

import java.io.*;

// Sin try-with-resources: tedioso y propenso a errores
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("datos.txt"));
    String linea = reader.readLine();
    System.out.println(linea);
} catch (IOException e) {
    System.out.println("Error: " + e.getMessage());
} finally {
    if (reader != null) {
        try { reader.close(); } catch (IOException e) { }
    }
}

// Con try-with-resources: limpio y seguro
try (BufferedReader reader = new BufferedReader(new FileReader("datos.txt"))) {
    String linea = reader.readLine();
    System.out.println(linea);
} catch (IOException e) {
    System.out.println("Error: " + e.getMessage());
}
// reader.close() se llama automáticamente

Cualquier clase que implemente AutoCloseable o Closeable puede usarse en try-with-resources.


8. Ejercicios

Ejercicio 1

Escribe un método parsearEntero(String texto) que:

  • Intente convertir el texto a int con Integer.parseInt
  • Si falla, capture NumberFormatException y devuelva un Optional<Integer> vacío
  • Si tiene éxito, devuelva el valor envuelto en Optional.of

Úsalo para procesar una lista de Strings mixtos (números y texto) sin que el programa falle.

Ejercicio 2

Crea una excepción personalizada EdadInvalidaException que extienda RuntimeException. Úsala en una clase Persona cuyo constructor rechace edades negativas o mayores de 150.

Ejercicio 3

Escribe un método que lea un archivo de texto línea por línea usando try-with-resources y devuelva el número de líneas. Si el archivo no existe, muestra un mensaje amigable y devuelve -1.


Errores Comunes

Error 1 — Capturar y silenciar la excepción

// Nunca hagas esto: tragas el error y no sabes qué pasó
try {
    // código
} catch (Exception e) {
    // vacío — el error desaparece sin dejar rastro
}

// Al menos registra el error
try {
    // código
} catch (Exception e) {
    System.err.println("Error inesperado: " + e.getMessage());
    e.printStackTrace(); // en producción usarías un logger
}

Error 2 — Usar excepciones para control de flujo normal

// Las excepciones son costosas; no las uses como if-else
try {
    int index = lista.indexOf("elemento");
    String elemento = lista.get(index);
} catch (IndexOutOfBoundsException e) {
    System.out.println("No existe");
}

// Verifica primero
if (lista.contains("elemento")) {
    String elemento = lista.get(lista.indexOf("elemento"));
}

Error 3 — Capturar la excepción en el lugar equivocado

// Capturar demasiado pronto: pierdes contexto
public static int dividir(int a, int b) {
    try {
        return a / b;
    } catch (ArithmeticException e) {
        return 0; // el llamador no sabe que hubo un error
    }
}

// Dejar que la excepción suba si el llamador puede manejarla mejor
public static int dividir(int a, int b) {
    if (b == 0) throw new IllegalArgumentException("El divisor no puede ser cero");
    return a / b;
}

Resumen

Concepto Para qué sirve
try-catch Capturar y manejar una excepción
finally Código que se ejecuta siempre (limpiar recursos)
throw Lanzar una excepción desde tu código
throws Declarar que un método puede lanzar una excepción verificada
Excepción personalizada Describir errores específicos de tu dominio
try-with-resources Cerrar recursos automáticamente

Próximos pasos

  • Genéricos: crea métodos y clases que trabajen con cualquier tipo sin perder seguridad de tipos
  • Colecciones: aplica el manejo de excepciones a operaciones con listas y mapas
  • Patrones de diseño: el patrón Result/Either es una alternativa funcional a las excepciones para errores esperados