Try-Catch es una construcción de captura de excepciones que permite ejecutar código potencialmente peligroso en un bloque protegido y manejar errores correctamente sin que el programa se cierre inesperadamente. El bloque try contiene código que puede lanzar una excepción, catch la intercepta y ejecuta la lógica de recuperación. Según la Apple Swift Documentation (2026), el bloque finally se ejecuta independientemente de si se lanzó una excepción o no, garantizando la liberación de recursos.
Puntos clave
Try-Catch es una construcción fundamental de manejo estructurado de excepciones presente en la mayoría de los lenguajes de programación modernos. Consta de tres bloques: try (intento de ejecución de código peligroso), catch (intercepción y manejo de la excepción) y finally opcional (finalización). La idea de la construcción es separar la lógica de negocio de la lógica de manejo de errores, haciendo el código más legible y predecible.
El concepto se implementó por primera vez en C++ como try/catch, luego fue adoptado por Java, C#, Swift, Kotlin, Dart, Python, JavaScript y otros lenguajes. Cada lenguaje añade sus propias características: en Swift el bloque catch debe ser exhaustivo, en Kotlin try-catch puede ser una expresión, en Dart finally es obligatorio para recursos de flujo. A pesar de las diferencias, el principio básico es el mismo: el error se maneja lo más cerca posible del lugar donde ocurre, no globalmente.
El uso de Try-Catch es especialmente importante en el desarrollo móvil, donde los factores externos — pérdida de red, respuesta incorrecta del servidor, memoria insuficiente — ocurren constantemente. Un manejo adecuado de excepciones evita cierres inesperados de la aplicación y garantiza una UX correcta: el usuario ve un mensaje de error en lugar de un cierre repentino. Según la Google Android Kotlin Style Guide (2026), cada función que pueda lanzar una excepción debe manejarla mediante try-catch o declarar throws en su firma.
El mecanismo de ejecución de try-catch se basa en el desenrollado de la pila (stack unwinding). Cuando se lanza una excepción dentro del bloque try mediante el operador throw (o como resultado de un error del sistema), el flujo normal de ejecución se interrumpe inmediatamente. La ejecución se mueve un nivel arriba en la pila de llamadas en busca de un bloque catch adecuado. Los lenguajes modernos buscan un catch cuyo tipo coincida con el tipo de la excepción lanzada mediante un mecanismo de coincidencia de tipos (type matching).
Si se encuentra un catch coincidente, se ejecuta su cuerpo, después de lo cual la ejecución continúa después de toda la construcción try-catch-finally. Si no se encuentra ningún catch, la excepción asciende por la pila y puede ser manejada en un nivel superior — hasta un manejador global que, en una aplicación móvil, muestra al usuario un diálogo de error. Si la excepción no se maneja en ninguna parte, la aplicación se cierra inesperadamente. Por eso el manejo correcto de todos los tipos posibles de excepciones es crítico para la estabilidad de la aplicación.
fun readUserData(): User {
return try {
val response = api.fetchUser()
parseUser(response)
} catch (e: IOException) {
logError("Error de red", e)
throw AppException("No se pudieron cargar los datos")
} catch (e: JsonParseException) {
logError("Error de análisis", e)
return User.default()
} finally {
closeLoadingIndicator()
}
}
El código primero intenta hacer una solicitud a la API y analizar la respuesta. Si ocurre una IOException (problema de red), la excepción se registra y se relanza como AppException. Si ocurre una JsonParseException — se devuelve un usuario por defecto. El bloque finally garantiza ocultar el indicador de carga, evitando fugas de componentes UI en la pantalla.
En Swift, el manejo de errores se implementa mediante el protocolo Error (anteriormente ErrorType). Cualquier tipo que conforme a Error puede lanzarse mediante el operador throw. Una función que puede lanzar un error se marca con la palabra clave throws en su firma. Llamar a dicha función requiere el prefijo try (para try-catch explícito), try? (resultado opcional) o try! (ejecución forzada sin manejo de errores).
enum NetworkError: Error {
case noConnection
case serverError(code: Int)
case timeout
}
func fetchUser(id: Int) throws -> User {
guard isConnected() else {
throw NetworkError.noConnection
}
let data = try performRequest(path: "/users/\(id)")
return try decodeUser(from: data)
}
do {
let user = try fetchUser(id: 42)
updateUI(user)
} catch NetworkError.noConnection {
showOfflineAlert()
} catch let error as NetworkError {
showError("Red " + error.localizedDescription)
} catch {
showGenericError()
}
El enum NetworkError implementa el protocolo Error, definiendo tres casos: noConnection, serverError con un código y timeout. La función fetchUser está marcada con throws: primero verifica la conexión, luego realiza la solicitud y el análisis. En el bloque do-catch, tres catch manejan diferentes escenarios: el caso específico noConnection, el tipo general NetworkError y todos los demás errores. Esto permite mostrar al usuario diferentes mensajes según el tipo de problema.
Kotlin heredó try-catch-finally de Java pero añadió una diferencia importante: en Kotlin, try-catch es una expresión (expression), no una instrucción (statement). Esto significa que el resultado de un bloque try o catch puede asignarse a una variable. La última expresión en el bloque try se convierte en el resultado en caso de éxito, la última expresión en el catch — en caso de error. Si el error no es manejado por ningún catch, la excepción asciende por la pila.
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
}
fun loadData(): Result<List<Item>> {
return try {
val response = api.getItems()
Result.Success(response.toList())
} catch (e: HttpException) {
Log.e("HTTP ", e)
Result.Error(e)
} catch (e: IOException) {
Log.e("Red ", e)
Result.Error(e)
}
}
En el ejemplo, sealed class Result envuelve una respuesta exitosa o un error. La función loadData usa try-catch como expresión: en caso de éxito devuelve Result.Success, en caso de HttpException o IOException — Result.Error con registro. Este enfoque permite al llamante manejar errores sin excepciones — mediante una expresión when sobre el tipo Result. Esto es especialmente conveniente en Jetpack Compose para mostrar diferentes estados de UI (Loading, Success, Error) mediante StateFlow y collectAsState.
Dart soporta try-catch-finally con una sintaxis similar a Java, pero con la adición de una cláusula on para filtrar por tipo de excepción sin especificar una variable. Esto es conveniente cuando la excepción en sí no es necesaria — solo importa el hecho de su tipo. Dart también soporta un bloque catch con dos parámetros: el objeto de excepción y StackTrace, útil para registrar la cadena completa de llamadas.
import 'dart:io';
import 'dart:convert';
class UserRepository {
Future<User> fetchUser(String id) async {
try {
final client = HttpClient();
final request = await client.getUrl(
Uri.parse('https://api.example.com/users/$id')
);
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
return User.fromJson(json.decode(body));
} on SocketException catch (e, stackTrace) {
log("No internet", e, stackTrace);
throw AppException("Connection failed");
} on FormatException {
throw AppException("Invalid response format");
} finally {
client.close();
}
}
}
SocketException se captura con el objeto de excepción y StackTrace para un registro detallado, y luego se relanza como AppException. FormatException se captura sin variable — basta con saber que el formato de la respuesta es incorrecto. El bloque finally garantiza cerrar HttpClient, evitando fugas de sockets. En Flutter, este enfoque es especialmente importante para las pruebas de Widget, donde las excepciones no manejadas en State.initState provocan la caída de toda la sesión de prueba.
Incluso los desarrolladores experimentados cometen errores al trabajar con try-catch que provocan fugas de memoria, errores ocultos o un comportamiento inadecuado de la aplicación. Veamos los cinco problemas más comunes en el desarrollo móvil.
El catch vacío es una de las peores prácticas. La excepción se traga, la aplicación continúa funcionando en un estado incorrecto y el desarrollador nunca se entera del problema. Siempre al menos registre la excepción. En Kotlin use catch(e: Exception) { Log.e(...) }, en Swift — catch { print($0) }. En Dart, un catch mínimamente aceptable debe llamar a debugPrint o escribir en Crashlytics.
Capturar todas las excepciones mediante catch (Exception e) sin distinguir por tipos oculta errores inesperados — NullPointerException, OutOfMemoryError, StackOverflowError. Capture solo los tipos que espera y puede manejar. Para todo lo demás, permita la propagación hacia arriba. En el desarrollo móvil, los catch específicos para IOException, TimeoutException, AuthException dan mensajes más significativos al usuario.
Los recursos — archivos, sockets, cursores de BD, animaciones — deben liberarse en finally o en un bloque use (AutoCloseable). Los desarrolladores a menudo olvidan cerrar los recursos cuando ocurre una excepción, lo que provoca fugas. En Kotlin use .use { } para recursos Closeable, en Swift — defer { }, en Dart — await using del paquete async. El bloque finally garantiza la liberación incluso cuando se lanza una excepción dentro de catch.
En código asíncrono, try-catch no captura excepciones de otros hilos. En Kotlin Coroutines use CoroutineExceptionHandler o SupervisorJob. En Swift async/await — do-catch dentro de Task. En Flutter — runZonedGuarded para captura global. Ignorar esta regla es la causa de cierres inesperados difíciles de reproducir en producción.
El manejo de excepciones no debe bloquear la interfaz de usuario indefinidamente. Muestre al usuario un mensaje específico y dele la oportunidad de reintentar la operación. Una Snackbar con botón Retry en Kotlin/Compose, UIAlertController con acción en Swift, SnackBar con acción en Flutter — una UX mínimamente suficiente para errores de red o servidor. Evite diálogos genéricos como “Ocurrió un error” sin opciones de recuperación.
Preguntas frecuentes
Try-catch usa excepciones y desenrollado de pila para el manejo de errores, lo que puede ser costoso en rendimiento cuando hay muchos errores. Result Type es un tipo contenedor (Success o Failure) que se maneja mediante pattern matching sin desenrollado de pila, siendo más eficiente para errores esperados.
Finally es obligatorio si el bloque try abre recursos (archivos, sockets, cursores) que deben cerrarse. Si no se abren recursos, finally no es necesario. En lenguajes modernos use AutoCloseable/use/defer para el cierre automático de recursos sin finally. El bloque use en Kotlin y Swift reemplaza finally para objetos Closeable.
En el flujo normal (sin excepciones), try-catch prácticamente no afecta el rendimiento — la JVM y el compilador de Swift optimizan este caso. Pero cuando se lanza una excepción, ocurre el desenrollado de pila, que puede tomar 10–100 μs dependiendo de la profundidad de la pila. No use excepciones para el control de flujo — es un antipatrón.
En corrutinas, use try-catch dentro de coroutineScope o CoroutineExceptionHandler para captura global. SupervisorJob evita que la corrutina padre se cancele cuando una corrutina hija falla. Para launch use CoroutineExceptionHandler, para async — try-catch alrededor de await().
Los múltiples catch son preferibles: el código se lee linealmente, cada bloque maneja un tipo de excepción. Un solo catch con if-else es más difícil de mantener y es fácil pasar por alto un nuevo tipo de excepción. En Swift los múltiples catch son obligatorios para el manejo exhaustivo de enum Error, en Kotlin no hay restricciones, pero la mejor práctica es un catch separado por tipo.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también