Throw/Throws — qué es, operadores de lanzamiento de excepciones y cómo funcionan en el desarrollo móvil

Autor: IT Sectr Publicado: 2026-05-26 Tiempo de lectura: 9 min

Throw y Throws son mecanismos para lanzar y declarar excepciones en lenguajes de programación. El operador throw interrumpe la ejecución normal de una función y pasa el objeto de error hacia arriba en la pila de llamadas. La palabra clave throws en la firma de una función advierte a la parte llamante sobre la posibilidad de un error, haciendo que el código sea predecible. Según la Documentación de Kotlin (2026), throw en Kotlin es una expresión, no una instrucción, lo que permite usarlo dentro de bloques when y el operador Elvis.

Puntos clave

  • Throw — un operador que interrumpe la ejecución de la función y pasa el objeto de error hacia arriba en la pila de llamadas
  • Throws — una palabra clave en la firma de la función que declara que la función puede lanzar una excepción
  • En Swift la función se marca con throws, la llamada requiere try/try?/try!, y throw acepta cualquier tipo que implemente el protocolo Error
  • En Kotlin throw es una expresión de tipo Nothing, throws está ausente (todas las excepciones son unchecked)
  • Errores personalizados se crean mediante enum (Swift) o sealed class (Kotlin) para un manejo conveniente en catch

¿Qué es Throw y Throws?

Throw es un operador que genera una excepción en el punto de ejecución del programa. Cuando se encuentra throw, el flujo de ejecución actual se interrumpe inmediatamente y el control se transfiere al manejador catch más cercano en la pila de llamadas. Si no se encuentra ningún manejador, la aplicación falla. Throw en el desarrollo móvil se utiliza para señalar errores que no pueden manejarse en el nivel actual de abstracción, por ejemplo, una respuesta de servidor no válida, ausencia de red o argumentos incorrectos.

Throws — un modificador en la firma de una función (principalmente en Swift) que declara que la función puede lanzar un error. Esto es parte del mecanismo de errores checked en Swift: la parte llamante debe manejar el error mediante do-catch, try?, try! o marcar su propia función como throws para su posterior propagación. En Kotlin y Java throws también existe, pero Kotlin lo considera redundante — todas las excepciones en Kotlin son unchecked, lo que significa que pueden no ser manejadas sin imposición sintáctica. Según la Documentación de Apple Swift (2026), throws en Swift es la única forma de declarar explícitamente la posibilidad de un error en el tipo de función, haciendo que los contratos de API sean transparentes para el desarrollador.

La diferencia entre throw y throws es fundamental: throw es una acción (lanzar una excepción en tiempo de ejecución), throws es una declaración (contrato en tiempo de compilación). Una función sin throws no puede usar throw — el compilador de Swift producirá un error. Una función con throws puede no usar throw — está permitido pero no tiene sentido. Esta separación convierte a throw/throws en una poderosa herramienta de diseño de API, donde el contrato de error es visible en la firma de la función antes de su llamada.

Throw en Swift

En Swift, el operador throw acepta cualquier tipo que implemente el protocolo Error. Lo más común es que sea un enum con casos para diferentes tipos de errores. Swift no admite excepciones checked al estilo Java — en su lugar, el tipo de función se marca con throws y el manejo se delega a la parte llamante. Esto hace que throw en Swift sea más flexible pero también más responsabilidad para el desarrollador.

Protocolo Error y tipos de error

Cualquier tipo que cumpla con el protocolo Error puede lanzarse mediante throw. Los desarrolladores suelen usar un enum con casos sin valores asociados (para errores simples) o con valores asociados (para pasar contexto). Swift no exige que el error sea un enum — se puede usar un struct o class que implemente Error, pero el enum es preferible gracias al exhaustive switch en el lado del manejador. El compilador verifica que todos los casos se manejen en do-catch.

swift
enum AuthError: Error {
    case invalidCredentials
    case tokenExpired
    case accountLocked(remainingMinutes: Int)
}

func login(username: String, password: String) throws -> Session {
    guard isValid(username) else {
        throw AuthError.invalidCredentials
    }
    let response = try api.authenticate(username, password)
    if response.isLocked {
        throw AuthError.accountLocked(
            remainingMinutes: response.lockDuration
        )
    }
    return Session(token: response.token)
}

AuthError define tres escenarios: credenciales inválidas, token caducado y cuenta bloqueada con un valor asociado de remainingMinutes. La función login se declara como throws — el compilador exige llamarla mediante try. Dentro de la función, throw se usa en dos lugares: para un nombre de usuario no válido y para una cuenta bloqueada. El valor asociado accountLocked permite pasar datos concretos al usuario — cuántos minutos esperar antes del desbloqueo. Este enfoque elimina la necesidad de endpoints API separados para verificar el estado del bloqueo.

Throw en Kotlin

En Kotlin throw es una expresión de tipo Nothing, no una instrucción. Esto significa que throw se puede usar en el lado derecho de una asignación, dentro de expresiones when y el operador Elvis ?:. El tipo Nothing es un subtipo especial de todos los tipos en Kotlin, lo que permite usar throw en lugares donde se requiere un valor de cualquier tipo. El compilador entiende que la ejecución no continúa después de throw y no requiere una rama para este caso.

Tipo Nothing y su papel en la composición

Nothing es un tipo único en Kotlin que es subtipo de todos los tipos posibles. Una función que devuelve Nothing (por ejemplo, TODO()) nunca se completa normalmente — o siempre lanza una excepción o entra en un bucle infinito. Esto hace que throw sea un candidato natural para usar en lugares donde se requiere un valor: operador Elvis, when sin else, inicialización de variables. Si throw está en una rama when, el compilador entiende que la rama lleva a Nothing y no requiere un return o else para esa rama.

kotlin
data class Config(val apiUrl: String, val timeoutSec: Int)

class ConfigParser {
    fun parse(json: String): Config {
        val obj = JSONObject(json)
        val url = obj.optString("apiUrl")
            ?: throw IllegalArgumentException("apiUrl is required")
        val timeout = obj.optInt("timeoutSec", 30)
        return Config(url, timeout)
    }

    fun getErrorMessage(code: Int): String {
        return when (code) {
            404 -> "Not found"
            500 -> "Server error"
            else -> throw IllegalArgumentException("Unknown code: $code")
        }
    }
}

En el primer ejemplo, throw se usa en el operador Elvis ?:: si el campo apiUrl falta en el JSON, la expresión throw interrumpe inmediatamente la ejecución y lanza una IllegalArgumentException. El tipo Nothing permite al compilador inferir el tipo del lado derecho como String (Elvis espera String, throw tiene tipo Nothing, Nothing es un subtipo de String). En el segundo ejemplo, throw se usa dentro de una expresión when: si el código no coincide con ninguno conocido, se lanza una excepción. El compilador entiende que el código después de throw es inalcanzable, por lo que el tipo de retorno de la función String no se viola.

Throws en la firma de función y rethrows

En Swift throws se especifica después de la lista de parámetros y antes de la flecha del tipo de retorno. Una función con throws solo puede llamar a otras funciones throws dentro de do-catch o con try?. Si una función throws no maneja el error, lo pasa a la parte llamante. Swift también admite rethrows — un modificador para funciones de orden superior que aceptan un closure throws y propagan su error. rethrows significa que la función solo lanza un error si el closure pasado lanzó uno — la función misma no genera un error.

swift
func mapValues<T>(
    _ array: [T],
    transform: (T) throws -> U
) rethrows -> [U] {
    var result = [U]()
    for element in array {
        result.append(try transform(element))
    }
    return result
}

// Uso con closure throws
let parsed = try mapValues(jsonStrings) { str in
    let data = Data(str.utf8)
    return try JSONDecoder().decode(Item.self, from: data)
}

rethrows permite que la función mapValues sea flexible: acepta tanto closures throws como normales. Si se pasa un closure throws, la llamada a mapValues requiere try; si es normal, try no es necesario. Esto hace que rethrows sea ideal para funciones de orden superior como map, filter, reduce en la biblioteca estándar de Swift. Recomendación de Apple: use rethrows para APIs que aceptan closures throws y la única fuente de error es ese closure. Si la función puede lanzar su propio error, use throws.

Excepciones checked vs unchecked

Swift throws está más cerca de las excepciones checked (como en Java) — los errores lanzados se declaran en la firma. Kotlin y Dart usan excepciones unchecked — throws en la firma no es necesario. La diferencia es fundamental: checked obliga al desarrollador a manejar el error (más seguro pero más verboso), unchecked da libertad pero aumenta el riesgo de olvidar manejar un error. Swift eligió checked para throws, Kotlin eligió unchecked para todas las excepciones. Ambos enfoques tienen ventajas: Swift es más confiable a nivel de lenguaje, Kotlin es más compacto y conveniente en cadenas de transformaciones funcionales.

Tipos de error personalizados

Para un manejo estructurado de errores en aplicaciones móviles, se recomienda crear tipos de error personalizados en lugar de usar Exception o Error base. En Swift se usa un enum con el protocolo Error; en Kotlin, una sealed class que herede de Throwable (o de Exception); en Dart, una clase que herede de Exception. Los tipos personalizados permiten agrupar errores por categorías y pasar datos asociados.

LenguajeTipo de errorCaracterística
Swiftenum: Error { ... }Valores asociados, exhaustive switch en catch
Kotlinsealed class : Throwable()Data class para errores con campos, expresión when
Dartclass implements ExceptionCampo Message, cláusula on en catch
Javaclass extends ExceptionChecked vs unchecked, throws obligatorio en firma

Al diseñar errores personalizados, siga la regla: un error — un escenario. No combine diferentes causas en un solo tipo con un flag String message — cree casos/subclases separados para cada escenario. Esto permitirá a la parte llamante manejar cada caso mediante pattern matching (when/switch) en lugar de comparación de cadenas. En Swift esto proporciona exhaustive checking — el compilador advertirá si algún caso del enum NetworkError no se maneja.

Ejemplo de error personalizado en Kotlin

kotlin
sealed class NetworkError(val message: String) : Throwable(message) {
    data class Timeout(val durationMs: Long) :
        NetworkError("Request timed out after ${durationMs}ms")
    data class HttpError(val code: Int, val body: String?) :
        NetworkError("HTTP $code")
    data class NoConnection(val cause: IOException) :
        NetworkError("No internet connection")
}

La sealed class NetworkError hereda de Throwable (el tipo estándar de excepción en Kotlin). Cada subclase es una data class con sus propios campos: Timeout contiene la duración del tiempo de espera en milisegundos, HttpError contiene el código y el cuerpo de la respuesta, NoConnection contiene la IOException original. Este diseño permite manejar cada error mediante when con coincidencia exhaustiva (cuando añade una nueva subclase, el compilador obligará a actualizar todas las expresiones when).

Try, try? y try! en Swift

Swift ofrece tres formas de llamar a funciones throws, cada una con su propio contrato de seguridad. try es la forma estándar: requiere do-catch o estar dentro de una función throws. try? convierte el error en nil — el resultado se vuelve opcional, en caso de error devuelve nil, el tipo cambia de T a T?. try! fuerza la ejecución sin manejo: si se lanza un error, la aplicación falla. Use try! solo cuando esté absolutamente seguro de que un error es imposible (por ejemplo, datos obviamente válidos).

swift
let configPath = Bundle.main.path(forResource: "config", ofType: "json")!

// try? — resultado opcional
let data = try? Data(contentsOf: URL(fileURLWithPath: configPath))
let json = try? JSONSerialization.jsonObject(with: data ?? Data())

// try! — éxito garantizado (solo cuando esté seguro)
let decoder = JSONDecoder()
let defaultConfig = try! decoder.decode(
    Config.self,
    from: Config.defaultJSON
)

// try — manejo estándar
do {
    let user = try fetchUser()
    showUser(user)
} catch let error as NetworkError {
    showRetryAlert(error.message)
}

En el ejemplo, try! se usa para JSON obviamente válido incluido en el bundle de la aplicación — el error de decodificación es imposible con un lanzamiento correcto. try? se usa para leer un archivo de configuración — si el archivo falta o está dañado, la aplicación usa valores predeterminados en lugar de fallar. try en do-catch se usa para solicitudes de red donde se espera un error y requiere respuesta del usuario. Recomendación: evite try! en código de producción — úselo solo para datos constantes verificados en tiempo de compilación.

Preguntas frecuentes

¿Cuál es la diferencia entre throw y throws en Swift?

Throw es un operador que lanza un error durante la ejecución del programa, interrumpiendo el flujo. Throws es un modificador de la firma de función que declara que la función puede lanzar un error. Una función sin throws no puede usar throw. Throws es un contrato en tiempo de compilación, throw es una acción en tiempo de ejecución.

¿Por qué no hay throws en Kotlin?

Kotlin sigue la filosofía de excepciones unchecked: todas las excepciones pueden no ser manejadas sin imposición sintáctica. Los desarrolladores de Kotlin consideran que throws en Java lleva a bloques try-catch excesivos y a ignorar excepciones checked mediante catch vacíos. El tipo Nothing de Kotlin permite usar throw como expresión, reemplazando throws con un enfoque más flexible.

¿Cuándo usar try! en Swift?

try! solo es aceptable cuando está absolutamente seguro de que un error es imposible: JSON obviamente válido del bundle, datos constantes, esquemas de URL correctos. En código de producción, try! es una excepción, no una regla. try? es preferible para escenarios opcionales con un valor de respaldo, try con do-catch para manejo obligatorio de errores.

¿Qué es rethrows en Swift?

Rethrows es un modificador para funciones que aceptan closures throws. Una función con rethrows solo lanza un error si el closure pasado lanzó uno. Esto permite que las funciones de orden superior (map, filter) funcionen tanto con closures throws como sin throws sin forzar try en la parte llamante.

¿Se puede lanzar un error dentro de un bloque catch?

Sí, dentro de catch se puede usar throw para propagar el error hacia arriba en la pila, envolviéndolo en un tipo diferente o añadiendo contexto. Esto se llama error chaining o rethrow. En Swift, simplemente otro throw dentro de catch es suficiente; en Kotlin, throw dentro de un bloque catch. El bloque finally se ejecuta antes de que el control se pase más arriba.

Resumen

  • Throw — un operador de lanzamiento de excepción que interrumpe el flujo actual y pasa el objeto de error hacia arriba en la pila
  • Throws — una declaración en tiempo de compilación en la firma de la función sobre la posibilidad de un error, obligatorio en Swift, ausente en Kotlin
  • En Swift throw acepta enum: Error, la función se marca con throws, se llama mediante try / try? / try!
  • En Kotlin throw es una expresión de tipo Nothing, lo que permite su uso en when, operador Elvis y asignaciones
  • Rethrows en Swift permite a las funciones de orden superior propagar errores solo desde los closures pasados
  • Errores personalizados se crean mediante enum (Swift) o sealed class (Kotlin) con valores asociados para información detallada
  • Diseñe tipos de error según el principio "un caso — un escenario" para un manejo conveniente mediante pattern matching

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.

Discutir el proyecto

Lea también