9. Gestión de excepciones: try-catch-finally
UD3 — Control de Flujo y Depuración · RA3 — Escribe y depura código, analizando y utilizando las estructuras de control del lenguaje
| Aspecto | Valor |
|---|---|
| Resultado de aprendizaje | RA: 3 — Escribe y depura código, analizando y utilizando las estructuras de control del lenguaje. |
| Criterios de evaluación a los que contribuye esta sección | CE 3.4 (escribe código utilizando control de excepciones) CE 3.5 (crea programas ejecutables combinando estructuras de control) CE 3.6 (prueba y depura los programas) CE 3.7 (comenta y documenta el código). |
| Marco normativo | RD 405/2023 · RD 450/2010 · RD 659/2023 |
| Tecnología base | Java 25 LTS (OpenJDK Temurin 25) — soporte hasta 2030+ |
| Horas estimadas | ~4 h (de las 30 h de la UD3). |
Requisitos previos (lo que ya sabes): condiciones booleanas y operadores lógicos (sección 1 de UD3);
if/else if/else, early return, validación conIO.readlny condicionales simples (sección 2 de UD3);switchexpression (sección 3 de UD3);whileydo-while(sección 4 de UD3); arrays unidimensionales (sección 5 de UD3);forclásico sobre arrays y Strings (sección 6 de UD3);for-each(sección 7 de UD3); bucles anidados, matricesint[][]y sentencias de salto (break,continue,return) — sección 8 de UD3. Tipos primitivos,var,String(con.equals(),.equalsIgnoreCase(),.length(),.trim(),.toLowerCase(),.charAt(),.isEmpty()),text blocks,finalpara constantes, operador ternario, conversión conInteger.parseInt/Double.parseDouble(de UD1). Conoces los nombres de las excepciones más comunes (NumberFormatException,ArrayIndexOutOfBoundsException,NullPointerException,ArithmeticException) porque las has visto pasar por la consola cuando tus programas se caían en secciones anteriores (asumíamos entrada válida). Aquí vamos a aprender a controlarlas en vez de sufrirlas.
9.1 ¿De qué va esta sección? — Dejar de temer los errores
Llegas a esta sección con un trauma: has escrito muchos programas que se han caído. Cuando el usuario escribía "abc" donde tú esperabas un número, veías algo como:
Y el programa terminaba. Sin mensaje amable para el usuario, sin oportunidad de volver a pedir el dato, sin limpiar recursos. Muerte súbita.
Esta sección va a cambiar eso. Vas a aprender a capturar esas excepciones y a reaccionar en vez de morir. La diferencia es enorme: un programa que se cae ante el primer error es frágil; un programa que maneja los errores es robusto y profesional.
La herramienta principal para ello es la sentencia try-catch-finally, que verás en detalle. Cubriremos, en este orden:
- ¿Qué es una excepción? La jerarquía
Throwable → Error / Exception. - Checked vs. unchecked: la distinción fundamental de Java.
try-catchbásico: capturar por tipo específico.- Orden de los
catch: de específico a general. catchmúltiple y multitipo (catch (TypeA | TypeB e)).finally: el bloque que siempre se ejecuta.- Relanzar con
throwy declarar conthrows**. - Información de la excepción:
getMessage(),getClass().getName(),printStackTrace(). - Las excepciones más comunes que has visto hasta ahora.
- Errores típicos del
try-catch. - Ejemplos resueltos paso a paso.
Vamos a empezar por entender qué es exactamente una excepción en Java.
9.2 ¿Qué es una excepción?
9.2.1 Definición
Una excepción es un evento que ocurre durante la ejecución de un programa y que interrumpe el flujo normal de las instrucciones. Cuando se produce una excepción, Java crea un objeto que contiene información sobre lo que ha pasado (el tipo de error, un mensaje, dónde ocurrió) y lo lanza (throws) por la pila de llamadas hasta que alguien lo captura (catches) o hasta que llega al principio del programa y este termina con un error.
En Java, todas las excepciones son objetos de clases que heredan de java.lang.Throwable. Eso significa que, técnicamente, una excepción es una instancia de una clase, con campos y métodos. Aquí solo vamos a capturarlas y manejarlas; la creación de tus propias excepciones (clases que heredan de Exception) se ve en UD4, cuando conozcas la herencia.
9.2.2 La jerarquía Throwable
flowchart TD
Throwable["`java.lang.Throwable`"]
Throwable --> Error["`Error`"]
Throwable --> Exception["`Exception`"]
Exception --> RuntimeException["`RuntimeException\n(unchecked)`"]
Exception --> IOException["`IOException\n(checked)`"]
Exception --> SQLException["`SQLException\n(checked)`"]
RuntimeException --> NumberFormatException
RuntimeException --> NullPointerException
RuntimeException --> ArrayIndexOutOfBoundsException
RuntimeException --> ArithmeticException
RuntimeException --> ClassCastException
RuntimeException --> IllegalArgumentException
IOException --> FileNotFoundException
Las dos ramas principales de Throwable:
Error: problemas graves que tu programa no debería intentar manejar. Ejemplos:OutOfMemoryError(se agotó la memoria),StackOverflowError(stack overflow por recursión infinita). No capturesError; significa que algo está muy mal y hay que arreglarlo, no atraparlo.Exception: problemas manejables que tu programa puede capturar y gestionar. Esta es la rama con la que vamos a trabajar.
Dentro de Exception, hay dos subramas importantes:
RuntimeExceptiony sus subclases: son unchecked (no obligatorias). Las verás en 9.3.- Cualquier otra
Exception(IOException,SQLException, etc.): son checked (obligatorias). Las verás en 9.3.
9.2.3 Anatomía de un error en consola
Cuando una excepción no es capturada, Java imprime la traza de pila (stack trace) y termina el programa:
Partes:
Exception in thread "main": indica en qué hilo ocurrió (casi siempremain).java.lang.NumberFormatException: el tipo de excepción (clase completa).For input string: "abc": el mensaje descriptivo que pasó el que la lanzó.- Líneas
at ...: la traza de pila. Cada línea indica un método que estaba en ejecución. La primera línea es donde se lanzó la excepción; las siguientes son los métodos llamadores, hasta llegar amain.
Aprender a leer esta traza es fundamental para depurar. La primera línea te dice qué pasó; la traza te dice dónde.
9.2.4 Excepción vs. error de compilación
Ojo: una excepción no es un error de compilación. Es un error en tiempo de ejecución. La diferencia:
| Tipo de error | Cuándo se detecta | Lo ves... |
|---|---|---|
| Error de compilación | Al compilar (antes de ejecutar). | En IntelliJ, con un subrayado rojo. El programa no se ejecuta. |
| Excepción | En tiempo de ejecución (mientras el programa corre). | En la consola, con la traza de pila. El programa se cae. |
Los errores de compilación los resuelves arreglando el código (añadir un ;, cambiar un tipo, etc.). Las excepciones las resuelves manejándolas (try-catch) o previniéndolas (validando antes con if, por ejemplo comprobando != null antes de acceder a una referencia o b != 0 antes de dividir).
9.3 Checked vs. unchecked: la distinción fundamental
Java divide las excepciones en dos grandes familias: checked y unchecked. Es una de las decisiones de diseño más importantes del lenguaje y conviene entenderla bien desde el principio.
9.3.1 Checked (excepciones comprobadas)
Una excepción checked es aquella que el compilador te obliga a manejar. Si llamas a un método que puede lanzar una excepción checked, tienes dos opciones:
- Capturarla con
try-catchen el método llamador. - Declararla en la firma del método con
throws(para que la maneje quien te llama).
Si no haces ninguna de las dos, el programa no compila. El compilador te obliga a pensar en el error antes de que ocurra.
Ejemplos de checked:
| Excepción | Cuándo la lanza |
|---|---|
IOException |
Operaciones de E/S: leer/escribir ficheros, sockets. |
FileNotFoundException |
Subclase de IOException: fichero no encontrado. |
SQLException |
Operaciones con bases de datos (JDBC). |
ClassNotFoundException |
Class.forName("...") cuando la clase no existe. |
InterruptedException |
Un hilo fue interrumpido mientras dormía o esperaba. |
Estas excepciones se ven en unidades posteriores: IOException en UD5 (ficheros), SQLException en UD9 (JDBC). Aquí las mencionamos porque necesitas entender la distinción.
9.3.2 Unchecked (excepciones no comprobadas)
Una excepción unchecked es aquella que el compilador no te obliga a manejar. Puedes capturarla si quieres, pero si no lo haces, el programa compila igualmente. Si ocurre en runtime, se propaga y, si nadie la captura, el programa se cae con la traza de pila.
Ejemplos de unchecked:
| Excepción | Cuándo la lanza |
|---|---|
RuntimeException |
Clase base de todas las unchecked. |
NullPointerException |
Llamar a un método o acceder a un campo de una referencia null. |
ArrayIndexOutOfBoundsException |
Acceder a un array con un índice fuera de rango. |
NumberFormatException |
Integer.parseInt("abc") cuando la cadena no es numérica. |
ArithmeticException |
División entera por cero (5 / 0). |
ClassCastException |
Casting a un tipo incompatible ((String) objeto). |
IllegalArgumentException |
Argumento inválido para un método. |
StringIndexOutOfBoundsException |
charAt(i) con i fuera de rango en un String. |
Estas son las que has estado viendo todo el curso. Son bugs del programador: indican que algo en tu código no está bien (no validaste una entrada, no comprobaste null, no controlaste un índice). La forma profesional de manejarlas es prevenir con if (validaciones) en vez de capturarlas con try-catch. Pero a veces no queda más remedio que capturarlas (por ejemplo, NumberFormatException en Integer.parseInt), como verás en los ejemplos.
9.3.3 Tabla comparativa
| Característica | Checked | Unchecked |
|---|---|---|
| ¿Quién la obliga a manejar? | El compilador. | Nadie (opcional). |
| ¿Hereda de...? | Exception (pero no de RuntimeException). |
RuntimeException (o Error). |
| ¿Cuándo se ve? | En tiempo de compilación (si no la manejas, no compila). | En tiempo de ejecución (solo cuando ocurre). |
| ¿Filosofía? | "El error es recuperable: el programa debería manejarlo." | "El error es un bug: arréglalo en el código." |
| ¿Ejemplos típicos? | IOException, SQLException. |
NullPointerException, NumberFormatException. |
| ¿Dónde se ven en el curso? | UD5 (IOException), UD9 (SQLException). | UD3, en todas partes. |
9.3.4 La regla práctica
"Checked: el compilador me obliga. Unchecked: yo decido."
Si llamas a un método que lanza IOException y no lo manejas, IntelliJ te marca error en rojo y el programa no compila. Si llamas a Integer.parseInt (que puede lanzar NumberFormatException, unchecked) y no lo manejas, IntelliJ no te marca nada; el programa compila y se cae en runtime si el usuario escribe "abc".
Esa diferencia define cómo trabajas con cada una:
- Checked: el compilador te recuerda "gestiona esto". No puedes olvidarlo.
- Unchecked: tienes que recordarlo tú mismo (validando con
ifo capturando contry-catch).
9.4 try-catch básico
9.4.1 Sintaxis
Partes:
try { ... }: el bloque donde pones el código que puede fallar. Java lo ejecuta normalmente; si no ocurre ninguna excepción, elcatchse ignora y el programa continúa.catch (TipoExcepcion nombre) { ... }: el bloque que se ejecuta solo si dentro deltryse lanza una excepción del tipoTipoExcepcion(o de una subclase). La excepción se asigna a la variablenombrepara que puedas usarla dentro delcatch.- Después del
catch, el programa continúa su ejecución normal. La excepción ya está manejada.
9.4.2 Ejemplo: pedir un número con Integer.parseInt
Posibles salidas:
Con "42":
Con "abc":
Paso a paso cuando el usuario escribe "abc":
- Java ejecuta el bloque
try. - Llega a
Integer.parseInt("abc"). parseIntdetecta que"abc"no es numérico y lanzaNumberFormatException.- Java interrumpe el
try(las líneas después deparseInten eltryno se ejecutan) y busca uncatchque acepteNumberFormatException. - Lo encuentra:
catch (NumberFormatException e). Ejecuta el bloque delcatch. - El programa continúa con la línea después del
try-catch(IO.println("Programa terminado.")).
Sin el try-catch, al escribir "abc" verías:
Y el programa terminaría sin mostrar "Programa terminado.". Con try-catch, el programa sigue viviendo y responde con elegancia.
9.4.3 Diagrama de flujo del try-catch
flowchart TD
Inicio([Inicio]) --> Try["Entra al try"]
Try --> Ejecuta["Ejecuta código del try"]
Ejecuta --> Excepcion{"¿Se lanzó\nexcepción?"}
Excepcion -->|No| Continua([Continúa tras el try-catch])
Excepcion -->|Sí| BuscaCatch["Busca catch compatible"]
BuscaCatch --> Encontrado{"¿Encontrado?"}
Encontrado -->|Sí| EjecutaCatch["Ejecuta código del catch"]
EjecutaCatch --> Continua
Encontrado -->|No| Propaga([Propaga la excepción\nfuera del método])
9.4.4 La variable del catch
La variable del catch (e en catch (NumberFormatException e)) contiene la excepción lanzada. Puedes usarla dentro del bloque para obtener información:
Vamos a ver estos métodos en la sección 9.10.
9.4.5 Solo se captura el tipo declarado
Si un try puede lanzar varios tipos de excepción, un catch (NumberFormatException e) solo captura NumberFormatException (y sus subclases). Si se lanza otra excepción, no la captura:
Para capturar varios tipos, necesitas varios catch (ver 9.5) o un catch multitipo (ver 9.6).
9.4.6 Error: capturar Exception demasiado genérico
A veces verás código que captura Exception (la superclase de todas):
Esto es una mala práctica porque:
- Captura cualquier excepción, incluso
NullPointerExceptionoArrayIndexOutOfBoundsExceptionque probablemente sean bugs que deberías arreglar, no atrapar. - Oculta errores: si hay un bug en tu código dentro del
try, elcatch (Exception e)lo traga y no te enteras. - El mensaje "Algo pasó" no es útil para el usuario ni para ti.
Regla: captura siempre el tipo más específico que esperas. Si esperas NumberFormatException, captura NumberFormatException, no Exception.
9.5 Orden de los catch: de específico a general
9.5.1 Varios catch en un mismo try
Puedes poner varios catch seguidos para manejar distintos tipos de excepción:
Java evalúa los catch en orden, de arriba abajo, y ejecuta el primero que sea compatible con la excepción lanzada. Los demás se ignoran.
9.5.2 Regla: específico antes que general
Como Java evalúa en orden, los catch más específicos tienen que ir antes que los más generales. Si los pones al revés, el compilador te da error:
Error del compilador:
Solución: pon los específicos primero:
9.5.3 ¿Cuándo poner un catch (Exception e) al final?
Poner un catch (Exception e) al final, después de los específicos, es legítimo como red de seguridad para excepciones que no anticipaste. Pero entonces es importante registrar la excepción (imprimir la traza) para que sepas que ocurrió algo inesperado:
Ese e.printStackTrace() es importante: si solo imprimes "Error inesperado", cuando un usuario te diga "el programa me dio un error" no tendrás forma de saber qué pasó. Con printStackTrace, verás en la consola la traza y podrás diagnosticar.
9.6 catch múltiple y multitipo
9.6.1 catch multitipo (Java 7+)
Si quieres manejar varios tipos de excepción de la misma forma, puedes usar la sintaxis multitipo con |:
Aquí un único catch captura dos tipos distintos (NumberFormatException y ArrayIndexOutOfBoundsException) y los trata igual. La variable e es del tipo común más específico (en este caso, RuntimeException, porque ambas heredan de ella).
Cuándo usarlo:
- Cuando dos excepciones distintas se manejan exactamente igual (mismo mensaje, misma recuperación).
- Cuando quieres reducir la repetición.
Cuándo NO usarlo:
- Cuando quieres mensajes distintos para cada excepción. En ese caso, usa varios
catchseparados (ver 9.5).
9.6.2 Restricciones del catch multitipo
Los tipos en un catch multitipo no pueden estar relacionados por herencia. Por ejemplo, NumberFormatException hereda de IllegalArgumentException, así que no puedes escribir:
El compilador da error porque es redundante: si capturas IllegalArgumentException, ya estás capturando NumberFormatException automáticamente.
9.6.3 Cuándo usar varios catch vs. multitipo
| Caso | Recomendación |
|---|---|
| Varias excepciones con mensajes distintos. | Varios catch separados. |
| Varias excepciones con el mismo manejo. | catch multitipo con \|. |
| Una sola excepción esperada. | Un solo catch específico. |
| Muchas excepciones distintas y un fallback genérico. | Varios catch específicos + catch (Exception e) al final. |
9.7 finally: el bloque que siempre se ejecuta
9.7.1 Sintaxis
finally es un bloque que se ejecuta siempre, ocurra lo que ocurra en el try:
- Si el
trytermina sin excepción →finallyse ejecuta. - Si el
trylanza una excepción y elcatchla captura →finallyse ejecuta. - Si el
trylanza una excepción y no haycatchque la capture →finallyse ejecuta (y luego la excepción se propaga). - Si dentro del
tryo delcatchhacesreturn→finallyse ejecuta antes de salir del método.
9.7.2 ¿Para qué sirve finally?
finally se usa para limpieza: cerrar ficheros, liberar recursos, liberar conexiones a base de datos, etc. Es decir, código que tiene que ejecutarse sí o sí, incluso si hubo un error.
Aquí tienes un ejemplo motivacional (no ejecutable todavía, porque Scanner se ve en UD2, pero ilustra la idea):
Sin finally, si parseInt lanzaba NumberFormatException, el sc.close() no se ejecutaría y el recurso quedaría abierto. Con finally, nos aseguramos de que se cierre siempre.
9.7.3 finally sin catch
finally puede aparecer sin catch: cuando no quieres manejar la excepción aquí, pero sí quieres limpiar recursos antes de propagarla:
Aquí finally se ejecuta, y luego la excepción se propaga (si la hubo). Útil cuando quieres limpiar pero dejar que la excepción la maneje alguien más arriba.
9.7.4 Error: finally que lanza excepción
Si dentro del finally lanzas otra excepción, se pierde la original:
Regla: mantén el finally simple (cerrar un recurso, una operación que no falle). Si tiene lógica compleja, refactoriza.
9.7.5 try-with-resources: la alternativa moderna
En Java 7+ se introdujo try-with-resources, que automatiza el finally para cerrar recursos. Es la forma moderna de manejar recursos y elimina la necesidad de escribir finally manualmente para cierre. Se verá en la unidad 5.
9.8 Relanzar con throw y declarar con throws
9.8.1 throw: lanzar una excepción
La sentencia throw lanza una excepción. Hasta ahora, las excepciones las lanzaba Java internamente (cuando parseInt recibía "abc", por ejemplo). Pero tú también puedes lanzarlas explícitamente:
Si el usuario escribe 150, verás:
Sintaxis: throw new TipoExcepcion("mensaje"); crea una nueva excepción (con new) y la lanza. El programa se interrumpe en ese punto y la excepción se propaga por la pila.
9.8.2 ¿Por qué lanzar una excepción en vez de usar if?
A veces es más limpio lanzar una excepción que devolver un código de error. Comparación:
Cada enfoque tiene su sitio:
- Devolver -1 es útil cuando "no encontrado" es un caso normal esperado.
- Lanzar excepción es útil cuando "no encontrado" es un error que el llamador debería manejar.
La creación de excepciones propias (como ElementoNoEncontradoException) se ve en UD4. Aquí usamos las predefinidas del API.
9.8.3 throws: declarar que un método puede lanzar una excepción checked
Si un método puede lanzar una excepción checked, debes declararlo con throws en la firma. Esto avisa al compilador (y al llamador) de que el método puede fallar de esa forma:
Si llamas a leerFichero desde otro método, el compilador te obliga a:
- Capturar la
IOExceptioncontry-catch, O - Declararla con
throws IOExceptiontambién en tu método.
Esto se ve a fondo en UD5 (cuando leas ficheros). Aquí lo mencionamos porque necesitas entender la palabra clave throws cuando la veas en la firma de un método.
9.8.4 Diferencia entre throw y throws
| Palabra | Dónde va | Qué hace |
|---|---|---|
throw |
Dentro de un método, como sentencia. | Lanza una excepción concreta (con new). |
throws |
En la firma de un método, después de los parámetros. | Declara qué excepciones checked puede lanzar el método. |
Memoriza: throw lanza; throws declara.
9.9 Información de la excepción
Cuando capturas una excepción, la variable del catch contiene información útil. Vamos a ver los métodos más importantes.
9.9.1 getMessage()
Devuelve el mensaje descriptivo de la excepción. Es lo que pasa el que la lanzó:
Útil para mostrar al usuario o para log. Es lo que más vas a usar.
9.9.2 getClass().getName()
Devuelve el nombre completo de la clase de la excepción:
Útil para depurar o para logging, pero normalmente no se lo muestras al usuario (no significa nada para él).
9.9.3 printStackTrace()
Imprime la traza de pila completa en la consola (en System.err). Es lo que ves cuando una excepción no capturada termina el programa:
Salida (aproximada):
printStackTrace() es muy útil durante el desarrollo para depurar. En producción, normalmente usas logging (UD5 / UD7) en vez de imprimir directamente, pero printStackTrace te vale para el curso.
9.9.4 getCause()
Algunas excepciones envuelven a otra excepción (la "causa"). getCause() devuelve la excepción original:
Lo verás sobre todo en UD5 y UD9, cuando trabajes con APIs que envuelven excepciones.
9.9.5 Resumen de métodos
| Método | Qué devuelve | Cuándo usarlo |
|---|---|---|
getMessage() |
Mensaje descriptivo. | Mostrar al usuario, logging básico. |
getClass().getName() |
Nombre completo de la clase. | Logging, depuración. |
printStackTrace() |
Traza completa en consola. | Depuración durante el desarrollo. |
getCause() |
Excepción original (si existe). | Cuando la excepción envuelve a otra. |
9.10 Las excepciones más comunes que has visto
Aquí tienes una lista de las excepciones unchecked más comunes que te aparecerán en este curso. Para cada una, cuándo ocurre y cómo evitarla.
9.10.1 NumberFormatException
Cuándo: Integer.parseInt("abc") o Double.parseDouble("xyz") cuando la cadena no se puede convertir a número.
Cómo evitarla: captúrala con try-catch para mostrar un mensaje amable y volver a pedir el dato (patrón que verás en 9.13.1). Esta es la forma profesional de validar entrada numérica y la que vas a usar a partir de ahora.
9.10.2 ArrayIndexOutOfBoundsException
Cuándo: acceder a arr[i] con i fuera del rango [0, arr.length - 1].
Cómo evitarla: comprueba i < arr.length antes de acceder (patrón del for clásico). Es la trampa nº 1 con arrays (ver sección 5).
9.10.3 NullPointerException
Cuándo: llamar a un método o acceder a un campo de una referencia null. Por ejemplo:
Cómo evitarla: comprueba != null antes de usar referencias que pueden ser null. En arrays de String inicializados con new, los elementos son null por defecto (ver sección 5).
9.10.4 ArithmeticException
Cuándo: división entera por cero (5 / 0). Con double no lanza excepción; devuelve Infinity o NaN.
Cómo evitarla: comprueba b != 0 antes de dividir. Es lo que hicimos en la calculadora de la sección 2.
9.10.5 StringIndexOutOfBoundsException
Cuándo: texto.charAt(i) con i fuera del rango [0, texto.length() - 1].
Cómo evitarla: comprueba i < texto.length() antes de acceder. Es el equivalente de ArrayIndexOutOfBoundsException para Strings.
9.10.6 ClassCastException
Cuándo: casting a un tipo incompatible. Por ejemplo:
Cómo evitarla: en UD3 no vas a usar casting (necesitas POO). Lo verás en UD7 con pattern matching, que es la forma moderna de evitarlo.
9.10.7 IllegalArgumentException
Cuándo: la lanza un método cuando le pasas un argumento inválido. Por ejemplo, Integer.parseInt lanza NumberFormatException, que hereda de IllegalArgumentException.
Cómo evitarla: lee la documentación del método para saber qué argumentos espera.
9.10.8 InputMismatchException (si usas Scanner)
Cuándo: sc.nextInt() cuando el usuario escribe algo que no es un entero (por ejemplo, "abc").
Cómo evitarla: en este curso usamos IO.readln() y convertimos con Integer.parseInt, así que no la verás mucho. Si usas Scanner.nextInt(), captúrala con try-catch o prevalida con sc.hasNextInt().
9.10.9 Tabla resumen
| Excepción | Tipo | Cuándo | Cómo evitarla |
|---|---|---|---|
NumberFormatException |
unchecked | parseInt("abc") |
try-catch (validación robusta de entrada) |
ArrayIndexOutOfBoundsException |
unchecked | arr[5] en un array de 3 |
i < arr.length |
NullPointerException |
unchecked | s.length() con s == null |
!= null |
ArithmeticException |
unchecked | 5 / 0 |
b != 0 |
StringIndexOutOfBoundsException |
unchecked | "hola".charAt(10) |
i < texto.length() |
ClassCastException |
unchecked | casting incompatible | (POO — UD7) |
IllegalArgumentException |
unchecked | argumento inválido | documentación del método |
InputMismatchException |
unchecked | Scanner.nextInt() con no-entero |
IO.readln + parseInt o hasNextInt |
IOException |
checked | E/S ficheros | UD5 (try-catch obligatorio) |
SQLException |
checked | JDBC | UD9 (try-catch obligatorio) |
9.11 Errores típicos del try-catch
Antes de pasar a los ejemplos, repasamos las trampas más típicas.
9.11.1 capturar Exception demasiado genérico
Ya la vimos en 9.4.6: captura todo, oculta bugs. Solución: captura el tipo específico que esperas.
9.11.2 orden de catch incorrecto
Ya la vimos en 9.5.2: el general antes que el específico da error de compilación. Solución: específico antes que general.
9.11.3 catch vacío (tragar la excepción)
Solución: siempre haz algo en el catch: muestra un mensaje, loguea, lanza de nuevo, o devuelve un valor por defecto. Un catch vacío es peor que no tener try-catch, porque oculta el error.
9.11.4 return dentro del try sin finally
Solución: usa try-with-resources para cierre automático, o pon el close() en un finally.
9.11.5 capturar para prevenir bugs en vez de arreglarlos
Regla: las unchecked son bugs. Arréglalas con if, no las captures. Las checked son errores esperados (fichero no existe, conexión caída): esas sí se capturan.
9.11.6 olvidar que finally se ejecuta siempre
Si pones un return dentro del try, el finally se ejecuta antes de salir:
Salida antes de devolver 10:
Solución: ten en cuenta el finally cuando uses return dentro de un try.
9.12 Ejemplos resueltos paso a paso
Cinco ejemplos completos que integran todo lo aprendido.
9.12.1 Ejemplo 1: pedir un número con try-catch y volver a pedir
Salida de ejemplo:
Puntos clave:
while (!valido): repite hasta que se consiga un número válido.try-catch: siparseIntlanzaNumberFormatException, elcatchmuestra el mensaje yvalidosiguefalse, así que el bucle se repite.- Si no lanza excepción,
numero = Integer.parseInt(entrada)tiene éxito yvalido = true, así que el bucle termina. Integer.parseIntlanza en12.5porque los decimales con punto no son enteros. Si quisieras aceptar decimales y convertir aint, haríasDouble.parseDoubley luego cast.
9.12.2 Ejemplo 2: varios catch para distintos errores
Salidas posibles:
| Entrada | Salida |
|---|---|
"1" |
Elemento: 20 |
"abc" |
Error: el índice debe ser un número entero. |
"5" |
Error: el índice debe estar entre 0 y 2. |
Puntos clave:
- Dos posibles excepciones en el
try:NumberFormatException(delparseInt) yArrayIndexOutOfBoundsException(delarr[i]). - Dos
catchespecíficos, cada uno con su mensaje. - El orden no importa aquí porque las dos excepciones son "hermanas" (no heredan una de otra), pero pon siempre las específicas antes que las generales.
9.12.3 Ejemplo 3: catch multitipo
Puntos clave:
catch (NumberFormatException | ArrayIndexOutOfBoundsException e): un solocatchcaptura ambos tipos.- Mensaje unificado: como ambos errores significan "entrada no válida", un único mensaje es suficiente.
ees del tipo común más específico (aquíRuntimeException, padre de ambas).
9.12.4 Ejemplo 4: finally para limpieza
Salida con "42":
Salida con "abc":
Puntos clave:
=== Fin del bloque try-catch ===se imprime siempre, tanto siparseInttuvo éxito como si lanzó excepción.finallyse ejecuta después deltry(si no hay excepción) o después delcatch(si la hay).- Aquí no hay recurso que cerrar, así que es un ejemplo didáctico. En la práctica,
finallyse usa parasc.close()o similar; pero para eso usamostry-with-resources.
9.13 Buenas prácticas del try-catch
9.13.1 Las 10 reglas del manejo de excepciones
- Captura el tipo específico que esperas, no
Exceptiona secas. - Orden de los
catch: específico antes que general. - Nunca dejes un
catchvacío: muestra un mensaje, loguea, o lanza de nuevo. - Las unchecked son bugs: arréglalas con
if, no las captures (salvo casos puntuales comoNumberFormatExceptionenparseInt). - Las checked son errores esperados: captúralas o decláralas con
throws. - Muestra mensajes útiles al usuario, no "Algo pasó". Incluye qué se esperaba y qué se recibió.
- En el catch, loguea la excepción:
e.printStackTrace()durante el desarrollo; logging en producción. - No captures para ignorar: si no sabes qué hacer con una excepción, mejor déjala propagar.
- Mantén el
trypequeño: solo lo que puede lanzar la excepción. No metas en eltrycódigo que no tiene nada que ver con la excepción.
9.13.2 Cuándo refactorizar
| Síntoma | Refactoriza a |
|---|---|
catch (Exception e) que captura todo. |
Varios catch específicos. |
catch vacío. |
Añadir mensaje, log, o lanza de nuevo. |
try-catch dentro de un while para validar. |
Es el patrón correcto: cuando llegues a UD2/UD6 conocerás String.matches para prevalidar sin excepción, pero para validar entrada numérica el try-catch es la forma estándar. |
catch que captura unchecked que es un bug. |
Arregla con if antes del try. |
9.13.3 Comentar el try-catch
9.14 Glosario rápido
| Término | Significado |
|---|---|
| Excepción | Evento que interrumpe el flujo normal; representado como un objeto de subclase de Throwable. |
Throwable |
Superclase de todas las excepciones y errores en Java. |
Error |
Subclase de Throwable para problemas graves no manejables (OutOfMemoryError, StackOverflowError). |
Exception |
Subclase de Throwable para problemas manejables. |
RuntimeException |
Subclase de Exception que agrupa las excepciones unchecked. |
| Checked | Excepción que el compilador obliga a manejar (IOException, SQLException). |
| Unchecked | Excepción que no obliga a manejar (NullPointerException, NumberFormatException). |
try |
Bloque que contiene código que puede lanzar una excepción. |
catch |
Bloque que se ejecuta si se lanza una excepción del tipo declarado. |
finally |
Bloque que se ejecuta siempre, haya o no excepción. |
throw |
Sentencia que lanza una excepción (con new). |
throws |
Declaración en la firma de un método: indica que puede lanzar una excepción checked. |
| Traza de pila (stack trace) | Lista de métodos por los que pasó la excepción; la imprime printStackTrace(). |
getMessage() |
Método de la excepción que devuelve el mensaje descriptivo. |
getClass().getName() |
Devuelve el nombre completo de la clase de la excepción. |
printStackTrace() |
Imprime la traza completa en System.err. |
getCause() |
Devuelve la excepción original (si la excepción envuelve a otra). |
NumberFormatException |
Unchecked: parseInt recibe una cadena no numérica. |
NullPointerException |
Unchecked: llamar a método de una referencia null. |
ArrayIndexOutOfBoundsException |
Unchecked: acceder a array con índice fuera de rango. |
ArithmeticException |
Unchecked: división entera por cero. |
ClassCastException |
Unchecked: casting a tipo incompatible. |
IOException |
Checked: error de E/S (ficheros, sockets) — UD5. |
SQLException |
Checked: error de base de datos — UD9. |
9.15 Actividades propuestas
Cinco actividades graduadas. Hazlas en orden. Las soluciones se publicarán en Aules.
9.15.1 Actividad 1 — Pedir un número hasta que sea válido
Objetivo: practicar try-catch con Integer.parseInt en un bucle.
Enunciado: Escribe un programa que pida al usuario un número entero hasta que escriba uno válido. Si escribe algo que no es un entero, muestra "Error: no es un número. Intenta de nuevo." y vuelve a pedirlo. Cuando sea válido, muestra el número y termina.
Requisitos:
- Usa
while (true)conbreak(sección 8) dentro deltrypara salir cuando el número sea válido. - Usa
try-catchconNumberFormatException. - Aquí sí queremos practicar
try-catch: el patrón "pedir hasta que sea válido" es el objetivo de la actividad. No prevalides con otros métodos.
Entrega: archivo PedirNumeroValido.java en el paquete jams.programacion.ud3.
Criterios evaluados: CE 3.4, CE 3.5, CE 3.7.
9.15.2 Actividad 2 — Calculadora con varios catch
Objetivo: practicar varios catch para distintos tipos de excepción.
Enunciado: Escribe una calculadora simple que pida dos números (como String) y una operación (+, -, *, /). Convierte los números con Double.parseDouble y muestra el resultado. Captura las siguientes excepciones con mensajes específicos:
NumberFormatException: "Error: debes escribir números válidos."ArithmeticException: "Error: división por cero." (aunquedouble / 0.0no lanzaArithmeticException, sí lo haceint / 0. Si usasdouble, comprueba el divisor conif (b == 0)en vez de capturar.)
Requisitos:
- Usa dos
catchseparados (no multitipo): uno paraNumberFormatException, uno paraArithmeticException. - Si la operación no es una de las cuatro, muestra "Operación no válida." (con
switchydefault). - Después de un error, el programa termina (no vuelve a pedir).
Entrega: archivo CalculadoraExcepciones.java en el paquete jams.programacion.ud3.
Criterios evaluados: CE 3.4, CE 3.5, CE 3.7.
9.15.3 Actividad 3 — catch multitipo y finally
Objetivo: combinar catch multitipo con finally.
Enunciado: Escribe un programa que:
- Pida un índice (como
String). - Conviértalo a
intconInteger.parseInt(puede lanzarNumberFormatException). - Acceda a
array[índice]dondearray = {10, 20, 30, 40, 50}(puede lanzarArrayIndexOutOfBoundsException). - Muestre el elemento si todo va bien.
- Use un
catchmultitipo paraNumberFormatException | ArrayIndexOutOfBoundsExceptioncon un mensaje unificado: "Entrada no válida: escribe un número entre 0 y 4." - Use un
finallyque imprima "Operación terminada." siempre, haya o no excepción.
Entrega: archivo CatchMultitipoFinally.java en el paquete jams.programacion.ud3.
Criterios evaluados: CE 3.4, CE 3.5, CE 3.7.
9.15.4 Actividad 4 — Cuestionario de repaso
Responde por escrito:
- ¿Qué es una excepción en Java?
- ¿Cuál es la diferencia entre
ErroryException? - ¿Cuál es la diferencia entre checked y unchecked? Pon un ejemplo de cada una.
- Escribe la sintaxis básica de
try-catch. - ¿Por qué los
catchespecíficos tienen que ir antes que los generales? - ¿Qué es un
catchmultitipo y cuándo lo usarías? - ¿Para qué sirve
finally? ¿Cuándo se ejecuta? - ¿Qué es
AutoCloseable? - ¿Cuál es la diferencia entre
throwythrows? - ¿Qué hace
e.getMessage(),e.getClass().getName()ye.printStackTrace()? - ¿Por qué nunca debes dejar un
catchvacío? - Nombra 5 excepciones unchecked y di cuándo se lanzan.
- ¿Por qué se dice que las unchecked son "bugs" y las checked son "errores esperados"?
- ¿Qué excepción lanza
Integer.parseInt("abc")? ¿Ynew int[3][5]accediendo a[3][0]?
Entrega: archivo cuestionario_seccion9.md con tus respuestas.
Criterios evaluados: CE 3.4, CE 3.7.
Cierre de la sección. Llegar hasta aquí es mérito. Has pasado de temer los errores a manejarlos con elegancia. Eso es un cambio de mentalidad importante: ya no escribes programas que "esperan que todo vaya bien", sino programas que anticipan que algo puede fallar y reaccionan en vez de morir. La regla más importante que te llevas de esta sección es: las unchecked son bugs, arréglalas con
if; las checked son errores esperados, captúralas contry-catch. Si interiorizas esa distinción, vas a escribir código mucho más robusto que la media de los principiantes. Y recuerda: nunca dejes uncatchvacío. Uncatchvacío es peor que no tenertry-catch, porque oculta el error. Nos vemos en la sección 10, donde vas a conocer el depurador de IntelliJ, la herramienta que va a cambiar tu forma de programar para siempre.