Manejo de errores y excepciones en Java: guía completa con ejemplos

📅 Actualizado en febrero 2026 ✍️ Ángel López ⏱️ 20 min de lectura ✓ Nivel intermedio ★ ★ ★ ★ ★ (5/5)

El manejo de errores y excepciones en Java es uno de los pilares fundamentales para escribir aplicaciones robustas y confiables. Una excepción (exception) es un evento anómalo que interrumpe el flujo normal de ejecución de un programa y que, si no se gestiona adecuadamente, provoca la terminación abrupta de la aplicación.

Java proporciona un mecanismo elegante basado en cinco palabras reservadas —try, catch, finally, throw y throws— que permite separar el código «normal» del código de tratamiento de errores, propagar excepciones por la pila de llamadas y clasificarlas en una jerarquía de clases. En este artículo se estudia todo el sistema de excepciones de Java con ejemplos ejecutables, buenas prácticas y ejercicios resueltos.

❓ ¿Qué es una excepción en Java?

Una excepción es un objeto que encapsula información sobre un error ocurrido durante la ejecución: su tipo (clase), un mensaje descriptivo y la traza de la pila de llamadas (stack trace) en el momento del fallo. Cuando un método detecta una situación excepcional, crea un objeto excepción y lo lanza (throw). El sistema de ejecución de Java (runtime) busca entonces hacia atrás en la pila de llamadas hasta encontrar un manejador adecuado (catch) que capture la excepción.

Si ningún método de la pila captura la excepción, la JVM muestra la traza completa en la salida de error estándar y termina el hilo (o el programa, si es el hilo principal).

💡 Ventajas del sistema de excepciones: (1) Separa el código de lógica del código de gestión de errores. (2) Permite propagar errores automáticamente por la pila de llamadas. (3) Agrupa y diferencia tipos de errores mediante la jerarquía de clases.

🌳 Jerarquía de excepciones en Java

Todas las excepciones y errores en Java son subclases de java.lang.Throwable. La jerarquía se divide en dos ramas principales:

ClaseTipoDescripciónEjemplos
ErrorErrores gravesProblemas graves del entorno de ejecución que no deben capturarse.OutOfMemoryError, StackOverflowError
ExceptionExcepciones checkedErrores recuperables. El compilador obliga a manejarlas.IOException, SQLException
RuntimeExceptionExcepciones uncheckedErrores de programación. No requieren manejo obligatorio.NullPointerException, ArithmeticException

Diagrama simplificado de la jerarquía

Jerarquía de clases
java.lang.Object
  └── java.lang.Throwable
        ├── java.lang.Error                    (NO capturar)
        │     ├── OutOfMemoryError
        │     ├── StackOverflowError
        │     └── ...
        └── java.lang.Exception                (CHECKED - capturar o declarar)
              ├── IOException
              │     └── FileNotFoundException
              ├── SQLException
              └── java.lang.RuntimeException    (UNCHECKED - no obligatorio)
                    ├── NullPointerException
                    ├── ArithmeticException
                    ├── ArrayIndexOutOfBoundsException
                    ├── IllegalArgumentException
                    └── ...

⚖️ Excepciones checked vs unchecked

Esta distinción es una de las características más importantes del sistema de excepciones de Java.

CaracterísticaCheckedUnchecked
Hereda deException (no RuntimeException)RuntimeException
VerificaciónEn tiempo de compilaciónEn tiempo de ejecución
ObligaciónHay que capturarla o declararla con throwsNo hay obligación
Causa típicaCondiciones externas: fichero no encontrado, conexión caídaErrores de programación: índice fuera de rango
EjemploIOException, SQLExceptionNullPointerException, ArithmeticException
⚠️ Regla práctica: Si el llamante puede recuperarse del error, usar checked. Si el error indica un fallo de programación, usar unchecked.

🛡️ Bloques try, catch y finally

El mecanismo básico de manejo de excepciones se compone de tres bloques:

BloqueObligatorioSe ejecuta cuando...
trySiempre. Contiene el código que puede lanzar excepciones.
catchAl menos uno (o un finally)Solo si se lanza una excepción del tipo especificado.
finallyNoSiempre, haya o no excepción. Ideal para liberar recursos.

Sintaxis básica

Java
try {
    int resultado = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("Error aritmético: " + e.getMessage());
} catch (Exception e) {
    System.out.println("Error genérico: " + e.getMessage());
} finally {
    System.out.println("Limpieza completada");
}
// Salida:
// Error aritmético: / by zero
// Limpieza completada

Múltiples bloques catch

Podemos encadenar varios bloques catch para manejar distintos tipos de excepción. Es fundamental ordenarlos de más específico a más general.

Java
public class MultipleCatch {
    public static void main(String[] args) {
        int[] numeros = {10, 20, 30};
        try {
            int valor = numeros[5];     // ArrayIndexOutOfBoundsException
            int resultado = valor / 0;  // ArithmeticException
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Índice fuera de rango: " + e.getMessage());
        } catch (ArithmeticException e) {
            System.out.println("Error aritmético: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("Otro error: " + e.getMessage());
        }
    }
}
// Salida: Índice fuera de rango: Index 5 out of bounds for length 3

🎯 throw y throws: lanzar y declarar excepciones

Es importante no confundir estas dos palabras reservadas:

Aspectothrowthrows
UbicaciónDentro del cuerpo de un métodoEn la firma del método
FunciónLanza una excepción explícitamenteDeclara que el método puede propagar una excepción
Sintaxisthrow new Exception("msg");void metodo() throws Exception { }
CantidadLanza una sola excepción cada vezPuede declarar varias, separadas por comas

Ejemplo: validación con throw y throws

Java
public class Validador {
    public static void validarEdad(int edad) throws IllegalArgumentException {
        if (edad < 0 || edad > 150) {
            throw new IllegalArgumentException(
                "Edad no válida: " + edad + ". Debe estar entre 0 y 150.");
        }
        System.out.println("Edad " + edad + " validada correctamente.");
    }

    public static void main(String[] args) {
        try {
            validarEdad(25);    // OK
            validarEdad(-5);    // Lanza excepción
            validarEdad(30);    // No se ejecuta
        } catch (IllegalArgumentException e) {
            System.out.println("Error de validación: " + e.getMessage());
        }
    }
}
// Salida:
// Edad 25 validada correctamente.
// Error de validación: Edad no válida: -5. Debe estar entre 0 y 150.

🔀 Multi-catch y try-with-resources

Desde Java 7, el lenguaje incorpora dos mejoras significativas: la captura múltiple (multi-catch) y la gestión automática de recursos (try-with-resources).

Multi-catch (Java 7+)

Java
try {
    String texto = null;
    int longitud = texto.length();   // NullPointerException
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
    System.out.println("Error: " + e.getClass().getSimpleName() + " - " + e.getMessage());
}
// Salida: Error: NullPointerException - null

Try-with-resources (Java 7+)

Cierra automáticamente los recursos que implementan AutoCloseable al finalizar el bloque.

Java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class LeerArchivo {
    public static void main(String[] args) {
        try (BufferedReader lector = new BufferedReader(new FileReader("datos.txt"))) {
            String linea;
            while ((linea = lector.readLine()) != null) {
                System.out.println(linea);
            }
        } catch (IOException e) {
            System.out.println("Error al leer archivo: " + e.getMessage());
        }
        // No es necesario lector.close() en un finally
    }
}
✅ Consejo: Siempre que se trabaje con recursos que requieran cierre (FileReader, Connection, Scanner), utilizar try-with-resources.

🔧 Crear excepciones personalizadas

Cuando las excepciones estándar de Java no describen el error del dominio de la aplicación, podemos crear nuestras propias clases extendiendo Exception (checked) o RuntimeException (unchecked).

Java
// Excepción checked personalizada
class SaldoInsuficienteException extends Exception {
    private double saldoActual;
    private double cantidadSolicitada;

    public SaldoInsuficienteException(double saldo, double cantidad) {
        super("Saldo insuficiente. Disponible: " + saldo + " €, solicitado: " + cantidad + " €");
        this.saldoActual = saldo;
        this.cantidadSolicitada = cantidad;
    }

    public double getSaldoActual() { return saldoActual; }
    public double getCantidadSolicitada() { return cantidadSolicitada; }
}

class CuentaBancaria {
    private String titular;
    private double saldo;

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

    public void retirar(double cantidad) throws SaldoInsuficienteException {
        if (cantidad > saldo) {
            throw new SaldoInsuficienteException(saldo, cantidad);
        }
        saldo -= cantidad;
        System.out.println("Retirada de " + cantidad + " €. Saldo: " + saldo + " €");
    }
}

public class EjemploExcepcionPersonalizada {
    public static void main(String[] args) {
        CuentaBancaria cuenta = new CuentaBancaria("Ana García", 1000.0);
        try {
            cuenta.retirar(300.0);    // OK
            cuenta.retirar(900.0);    // Lanza SaldoInsuficienteException
        } catch (SaldoInsuficienteException e) {
            System.out.println("Error: " + e.getMessage());
            System.out.println("Faltan: " + (e.getCantidadSolicitada() - e.getSaldoActual()) + " €");
        }
    }
}
// Salida:
// Retirada de 300.0 €. Saldo: 700.0 €
// Error: Saldo insuficiente. Disponible: 700.0 €, solicitado: 900.0 €
// Faltan: 200.0 €

✅ Buenas prácticas en el manejo de excepciones

PrácticaDescripción
Capturar excepciones específicasUsar catch (IOException e) en lugar de catch (Exception e).
No silenciar excepcionesUn catch vacío es un antipatrón grave. Al mínimo, registrar el error.
Usar try-with-resourcesPara ficheros, conexiones, streams. Evita fugas de recursos.
Mensajes descriptivosIncluir contexto útil: throw new IOException("No se pudo abrir: " + ruta).
No usar excepciones para control de flujoLas excepciones son para situaciones excepcionales, no para lógica habitual.
Documentar con JavadocDocumentar con @throws en el Javadoc del método.
Preferir unchecked para errores de programaciónUsar IllegalArgumentException, IllegalStateException, etc.

Antipatrón vs buena práctica

Java — ❌ Antipatrón: catch vacío
try {
    conexion.ejecutarConsulta(sql);
} catch (SQLException e) {
    // ❌ NUNCA: el error se pierde completamente
}
Java — ✅ Buena práctica
try {
    conexion.ejecutarConsulta(sql);
} catch (SQLException e) {
    System.err.println("Error en consulta: " + sql);
    e.printStackTrace();
    throw new PersistenciaException("Fallo en consulta", e);
}

🏗️ Ejemplo completo integrador

El siguiente programa simula un sistema de pedidos online que combina todos los conceptos: excepciones personalizadas, try-catch-finally, throw/throws, multi-catch y buenas prácticas.

Java
// Excepción checked: error de negocio
class ProductoNoDisponibleException extends Exception {
    public ProductoNoDisponibleException(String producto) {
        super("Producto agotado: " + producto);
    }
}

// Excepción unchecked: error de programación
class PedidoInvalidoException extends RuntimeException {
    public PedidoInvalidoException(String motivo) {
        super("Pedido inválido: " + motivo);
    }
}

class Inventario {
    private String[] productos = {"Portátil", "Teclado", "Ratón", "Monitor"};
    private int[] stock = {5, 10, 0, 3};

    public void verificarDisponibilidad(String producto, int cantidad)
            throws ProductoNoDisponibleException {
        for (int i = 0; i < productos.length; i++) {
            if (productos[i].equalsIgnoreCase(producto)) {
                if (stock[i] < cantidad) {
                    throw new ProductoNoDisponibleException(
                        producto + " (disponible: " + stock[i]
                        + ", solicitado: " + cantidad + ")");
                }
                stock[i] -= cantidad;
                System.out.println("✓ Reservado: " + cantidad + "x " + producto);
                return;
            }
        }
        throw new ProductoNoDisponibleException(producto + " (no existe en catálogo)");
    }
}

class Pedido {
    private String cliente;
    private String producto;
    private int cantidad;
    private double precioUnitario;

    public Pedido(String cliente, String producto, int cantidad, double precio) {
        if (cliente == null || cliente.isEmpty()) {
            throw new PedidoInvalidoException("El nombre del cliente no puede estar vacío");
        }
        if (cantidad <= 0) {
            throw new PedidoInvalidoException("La cantidad debe ser mayor que 0");
        }
        this.cliente = cliente;
        this.producto = producto;
        this.cantidad = cantidad;
        this.precioUnitario = precio;
    }

    public String getProducto() { return producto; }
    public int getCantidad() { return cantidad; }
    public double getTotal() { return cantidad * precioUnitario; }

    @Override
    public String toString() {
        return String.format("Pedido[%s, %dx %s, Total: %.2f €]",
                             cliente, cantidad, producto, getTotal());
    }
}

public class SistemaPedidos {
    public static void main(String[] args) {
        Inventario inventario = new Inventario();

        Object[][] datosPedidos = {
            {"Ana",    "Teclado",  2, 29.99},
            {"",       "Portátil", 1, 899.0},   // Cliente vacío
            {"Carlos", "Ratón",    3, 15.50},    // Sin stock
            {"David",  "Monitor",  1, 249.99},
            {"Elena",  "Tablet",   1, 199.0},    // No existe
        };

        int procesados = 0, fallidos = 0;

        for (Object[] datos : datosPedidos) {
            System.out.println("\n--- Procesando pedido ---");
            try {
                Pedido pedido = new Pedido(
                    (String) datos[0], (String) datos[1],
                    (int) datos[2], (double) datos[3]);
                System.out.println("Pedido creado: " + pedido);

                inventario.verificarDisponibilidad(
                    pedido.getProducto(), pedido.getCantidad());

                System.out.println("✅ Pedido confirmado: "
                    + String.format("%.2f", pedido.getTotal()) + " €");
                procesados++;
            } catch (PedidoInvalidoException | ProductoNoDisponibleException e) {
                System.out.println("❌ " + e.getMessage());
                fallidos++;
            } finally {
                System.out.println("   [Registro de auditoría actualizado]");
            }
        }

        System.out.println("\n=== RESUMEN ===");
        System.out.println("Procesados: " + procesados);
        System.out.println("Fallidos:   " + fallidos);
    }
}
// Salida:
// --- Procesando pedido ---
// Pedido creado: Pedido[Ana, 2x Teclado, Total: 59.98 €]
// ✓ Reservado: 2x Teclado
// ✅ Pedido confirmado: 59.98 €
//    [Registro de auditoría actualizado]
// --- Procesando pedido ---
// ❌ Pedido inválido: El nombre del cliente no puede estar vacío
//    [Registro de auditoría actualizado]
// --- Procesando pedido ---
// Pedido creado: Pedido[Carlos, 3x Ratón, Total: 46.50 €]
// ❌ Producto agotado: Ratón (disponible: 0, solicitado: 3)
//    [Registro de auditoría actualizado]
// --- Procesando pedido ---
// Pedido creado: Pedido[David, 1x Monitor, Total: 249.99 €]
// ✓ Reservado: 1x Monitor
// ✅ Pedido confirmado: 249.99 €
//    [Registro de auditoría actualizado]
// --- Procesando pedido ---
// Pedido creado: Pedido[Elena, 1x Tablet, Total: 199.00 €]
// ❌ Producto agotado: Tablet (no existe en catálogo)
//    [Registro de auditoría actualizado]
// === RESUMEN ===
// Procesados: 2
// Fallidos:   3

⚠️ Errores frecuentes

Error 1: Catch vacío que silencia el error

Java — ❌ Incorrecto
try {
    int resultado = Integer.parseInt(texto);
} catch (NumberFormatException e) {
    // ❌ El error desaparece sin dejar rastro
}
Java — ✅ Correcto
try {
    int resultado = Integer.parseInt(texto);
} catch (NumberFormatException e) {
    System.err.println("Formato no válido: '" + texto + "' - " + e.getMessage());
}

Error 2: Capturar Exception genérico demasiado pronto

Java — ❌ Incorrecto
try { leerArchivo(); procesarDatos(); }
catch (Exception e) { System.out.println("Algo falló"); } // ❌ Captura TODO
Java — ✅ Correcto
try { leerArchivo(); procesarDatos(); }
catch (FileNotFoundException e) { System.out.println("Archivo no encontrado"); }
catch (IOException e) { System.out.println("Error de lectura"); }

Error 3: Confundir throw con throws

Java — ❌ Incorrecto
public void metodo() {
    throws new IOException("Error"); // ❌ throws es para la firma
}
Java — ✅ Correcto
public void metodo() throws IOException {
    throw new IOException("Error de E/S"); // ✅ throw lanza, throws declara
}

Error 4: Ordenar catches de general a específico

Java — ❌ Error de compilación
try { leerArchivo(); }
catch (Exception e) { }      // ❌ Más general primero
catch (IOException e) { }     // ❌ Inalcanzable
Java — ✅ Correcto
try { leerArchivo(); }
catch (IOException e) { }     // ✅ Más específico primero
catch (Exception e) { }       // ✅ Más general después

📝 Ejercicios prácticos

Ejercicio 1: ¿Qué imprime este código? (comprensión)

Java
public class Ejercicio1 {
    public static void main(String[] args) {
        System.out.println("A");
        try {
            System.out.println("B");
            int x = 10 / 0;
            System.out.println("C");
        } catch (ArithmeticException e) {
            System.out.println("D");
        } finally {
            System.out.println("E");
        }
        System.out.println("F");
    }
}

Ejercicio 2: Conversor seguro de texto a número (aplicación)

Escribe un método static int convertirSeguro(String texto, int valorPorDefecto) que intente convertir una cadena a entero. Si falla, devuelve el valor por defecto. Prueba con: "42", "abc", null, "".

Ejercicio 3: Sistema de registro con excepciones personalizadas (diseño)

Crea una excepción checked RegistroException con código de error y un método registrar(String nombre, String email, int edad) que valide: nombre no vacío (E001), email con @ (E002), edad 18-120 (E003). Si falla, lanza RegistroException.

❓ Preguntas frecuentes sobre Manejo de errores y excepciones en Java: guía completa con ejemplos

Las dudas más comunes respondidas de forma clara y directa.

throw se usa dentro del cuerpo de un método para lanzar explícitamente una excepción (throw new Exception()). throws se usa en la firma del método para declarar que ese método puede propagar una excepción al código que lo invoca (void metodo() throws IOException). throw crea la excepción; throws la anuncia.
Las excepciones checked (como IOException o SQLException) son verificadas en tiempo de compilación: el programador debe manejarlas con try-catch o declararlas con throws. Las unchecked (como NullPointerException o ArrayIndexOutOfBoundsException) heredan de RuntimeException y no obligan a un tratamiento explícito, aunque se recomienda prevenirlas.
Sí. El bloque finally se ejecuta siempre, incluso si dentro del try o del catch hay una sentencia return. La única excepción es si se invoca System.exit(), que termina la JVM inmediatamente y no ejecuta el finally.
Se recomienda crear excepciones propias cuando las excepciones estándar de Java no describen con precisión el error del dominio de la aplicación. Por ejemplo, SaldoInsuficienteException para una cuenta bancaria o ProductoNoEncontradoException para un catálogo. Se debe extender Exception (checked) o RuntimeException (unchecked) según la naturaleza del error.
try-with-resources (añadido en Java 7) es una variante de try que cierra automáticamente los recursos que implementan AutoCloseable al finalizar el bloque. Se usa siempre que se trabaja con recursos que requieren cierre explícito: ficheros (FileReader), conexiones (Connection), flujos (InputStream), etc. Evita fugas de recursos y código boilerplate en el finally.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Manejo de errores y excepciones en Java: guía completa con ejemplos? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!