Excepciones personalizadas

Frente a errores del sistema

Excepción personalizada unchecked

La forma más habitual en código moderno. Extiende RuntimeException. Incluye siempre dos constructores: uno con solo el mensaje (para creación directa) y otro con mensaje y causa (para envolver excepciones de capas inferiores). Añade campos con la información de contexto relevante para el diagnóstico.

// Excepción personalizada unchecked (la más habitual)
public class PedidoNoEncontradoException extends RuntimeException {
    private final Long pedidoId;

    public PedidoNoEncontradoException(Long pedidoId) {
        super("Pedido no encontrado con id: " + pedidoId);
        this.pedidoId = pedidoId;
    }

    // Constructor para envolver una causa
    public PedidoNoEncontradoException(Long pedidoId, Throwable causa) {
        super("Pedido no encontrado con id: " + pedidoId, causa);
        this.pedidoId = pedidoId;
    }

    public Long getPedidoId() { return pedidoId; }
}

// Uso:
Pedido buscarPedido(Long id) {
    return repositorio.findById(id)
        .orElseThrow(() -> new PedidoNoEncontradoException(id));
}

Excepción personalizada checked

Extiende Exception. Solo tiene sentido cuando el llamador puede tomar una acción específica para recuperarse. La firma del método que la lanza debe declararla con throws.

// Excepción personalizada checked (cuando el llamador debe recuperarse)
public class ConexionExpiradaException extends Exception {
    private final String servidor;

    public ConexionExpiradaException(String servidor) {
        super("Conexión expirada con servidor: " + servidor);
        this.servidor = servidor;
    }

    public ConexionExpiradaException(String servidor, Throwable causa) {
        super("Conexión expirada con servidor: " + servidor, causa);
        this.servidor = servidor;
    }

    public String getServidor() { return servidor; }
}

// El llamador DEBE manejarla — el compilador lo obliga
void ejecutarConsulta() throws ConexionExpiradaException {
    if (!conexion.isValid()) throw new ConexionExpiradaException(conexion.getHost());
    // ...
}

Jerarquía de excepciones de dominio

En aplicaciones medianas y grandes es útil crear una excepción base de dominio de la que deriven todas las demás. Permite que los manejadores globales (por ejemplo, en Spring MVC) capturen todos los errores de dominio con un solo catch.

// Jerarquía de excepciones de dominio: útil en aplicaciones grandes
public class AplicacionException extends RuntimeException {
    public AplicacionException(String mensaje) { super(mensaje); }
    public AplicacionException(String mensaje, Throwable causa) { super(mensaje, causa); }
}

public class EntidadNoEncontradaException extends AplicacionException {
    public EntidadNoEncontradaException(String tipo, Object id) {
        super(tipo + " con id " + id + " no encontrado");
    }
}

public class OperacionNoPermitidaException extends AplicacionException {
    public OperacionNoPermitidaException(String motivo) {
        super("Operación no permitida: " + motivo);
    }
}

// Ventaja: los manejadores de excepción globales (ej: Spring @ExceptionHandler)
// pueden capturar AplicacionException para manejar todos los errores de dominio

Cuándo crear excepciones propias

// Cuándo crear excepciones personalizadas:

// SÍ: cuando la excepción representa un concepto del dominio
// y el llamador necesita distinguirla de otras excepciones
public class SaldoInsuficienteException extends RuntimeException {
    private final double saldoActual;
    private final double importeSolicitado;
    // El llamador puede mostrar mensajes específicos o reintentar con menor importe
}

// NO: cuando IllegalArgumentException, IllegalStateException o NoSuchElementException bastan
// Antes de crear una excepción personalizada, comprobar si alguna de las estándar encaja.

// Excepciones estándar útiles que suelen olvidarse:
// NoSuchElementException  → elemento no encontrado en una colección
// ConcurrentModificationException → modificación durante iteración
// UnsupportedOperationException   → operación no implementada
// ArithmeticException             → error matemático (división por cero)
Cuatro constructores estándar: las buenas excepciones tienen cuatro constructores (String, Throwable, String+Throwable, sin parámetros). En la práctica con dos basta: mensaje solo y mensaje+causa. Lo imprescindible es nunca perder la causa al envolver excepciones — new MiException("mensaje", causaOriginal).

Siguiente sección → Acceso a bases de datos con JDBC

Índice de la sección

Índice del curso