Try-Catch é uma construção de captura de exceções que permite executar código potencialmente perigoso em um bloco protegido e lidar com erros corretamente sem encerrar o programa abruptamente. O bloco try contém código que pode lançar uma exceção, catch a intercepta e executa a lógica de recuperação. De acordo com a Apple Swift Documentation (2026), o bloco finally executa independentemente de uma exceção ter sido lançada ou não, garantindo a liberação de recursos.
Pontos principais
Try-Catch é uma construção fundamental de tratamento estruturado de exceções presente na maioria das linguagens de programação modernas. Consiste em três blocos: try (tentativa de executar código perigoso), catch (interceptação e tratamento da exceção) e finally opcional (finalização). A ideia da construção é separar a lógica de negócio da lógica de tratamento de erros, tornando o código mais legível e previsível.
O conceito foi implementado pela primeira vez em C++ como try/catch, depois foi adotado por Java, C#, Swift, Kotlin, Dart, Python, JavaScript e outras linguagens. Cada linguagem adiciona suas próprias características: em Swift o bloco catch deve ser exaustivo, em Kotlin try-catch pode ser uma expressão, em Dart finally é obrigatório para recursos de fluxo. Apesar das diferenças, o princípio básico é o mesmo: o erro é tratado o mais próximo possível do local de ocorrência, não globalmente.
O uso de Try-Catch é especialmente importante no desenvolvimento mobile, onde fatores externos — perda de rede, resposta incorreta do servidor, memória insuficiente — ocorrem constantemente. O tratamento adequado de exceções evita crashes do aplicativo e garante uma UX correta: o usuário vê uma mensagem de erro em vez de um fechamento repentino. De acordo com o Google Android Kotlin Style Guide (2026), toda função que pode lançar uma exceção deve tratá-la via try-catch ou declarar throws em sua assinatura.
O mecanismo de execução do try-catch é baseado no desenrolamento da pilha (stack unwinding). Quando uma exceção é lançada dentro do bloco try através do operador throw (ou como resultado de um erro de sistema), o fluxo normal de execução é imediatamente interrompido. A execução sobe um nível na pilha de chamadas em busca de um bloco catch adequado. As linguagens modernas procuram um catch cujo tipo corresponda ao tipo da exceção lançada usando um mecanismo de correspondência de tipos (type matching).
Se um catch correspondente for encontrado, seu corpo é executado, após o que a execução continua após toda a construção try-catch-finally. Se nenhum catch for encontrado, a exceção sobe pela pilha e pode ser tratada em um nível superior — até um manipulador global que, em um aplicativo mobile, mostra ao usuário um diálogo de erro. Se a exceção não for tratada em lugar nenhum, o aplicativo trava. É por isso que o tratamento correto de todos os tipos possíveis de exceções é crítico para a estabilidade do aplicativo.
fun readUserData(): User {
return try {
val response = api.fetchUser()
parseUser(response)
} catch (e: IOException) {
logError("Erro de rede", e)
throw AppException("Falha ao carregar dados")
} catch (e: JsonParseException) {
logError("Erro de análise", e)
return User.default()
} finally {
closeLoadingIndicator()
}
}
O código primeiro tenta fazer uma requisição à API e analisar a resposta. Se ocorrer uma IOException (problema de rede), a exceção é registrada e relançada como AppException. Se ocorrer uma JsonParseException — um usuário padrão é retornado. O bloco finally garante ocultar o indicador de carregamento, evitando vazamentos de componentes de UI na tela.
Em Swift, o tratamento de erros é implementado através do protocolo Error (anteriormente ErrorType). Qualquer tipo que esteja em conformidade com Error pode ser lançado através do operador throw. Uma função que pode lançar um erro é marcada com a palavra-chave throws em sua assinatura. Chamar tal função requer o prefixo try (para try-catch explícito), try? (resultado opcional) ou try! (execução forçada sem tratamento de erro).
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("Rede " + error.localizedDescription)
} catch {
showGenericError()
}
O enum NetworkError implementa o protocolo Error, definindo três casos: noConnection, serverError com um código e timeout. A função fetchUser é marcada com throws: primeiro verifica a conexão, depois realiza a requisição e a análise. No bloco do-catch, três catch tratam diferentes cenários: o caso específico noConnection, o tipo geral NetworkError e todos os outros erros. Isso permite mostrar ao usuário diferentes mensagens dependendo do tipo de problema.
Kotlin herdou try-catch-finally de Java mas adicionou uma diferença importante: em Kotlin, try-catch é uma expressão (expression), não uma instrução (statement). Isso significa que o resultado de um bloco try ou catch pode ser atribuído a uma variável. A última expressão no bloco try torna-se o resultado em caso de sucesso, a última expressão no catch — em caso de erro. Se o erro não for tratado por nenhum catch, a exceção sobe pela pilha.
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("Rede ", e)
Result.Error(e)
}
}
No exemplo, sealed class Result encapsula uma resposta bem-sucedida ou um erro. A função loadData usa try-catch como expressão: em caso de sucesso retorna Result.Success, em caso de HttpException ou IOException — Result.Error com registro. Esta abordagem permite ao chamador tratar erros sem exceções — através de uma expressão when sobre o tipo Result. Isso é especialmente conveniente em Jetpack Compose para exibir diferentes estados de UI (Loading, Success, Error) através de StateFlow e collectAsState.
Dart suporta try-catch-finally com sintaxe semelhante ao Java, mas com a adição de uma cláusula on para filtrar por tipo de exceção sem especificar uma variável. Isso é conveniente quando a exceção em si não é necessária — apenas o fato de seu tipo importa. Dart também suporta um bloco catch com dois parâmetros: o objeto de exceção e StackTrace, útil para registrar a cadeia completa de chamadas.
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 é capturada com o objeto de exceção e StackTrace para registro detalhado, e então relançada como AppException. FormatException é capturada sem variável — basta saber que o formato da resposta está incorreto. O bloco finally garante fechar o HttpClient, evitando vazamentos de sockets. Em Flutter, esta abordagem é especialmente importante para testes de Widget, onde exceções não tratadas em State.initState causam a falha de toda a sessão de teste.
Mesmo desenvolvedores experientes cometem erros ao trabalhar com try-catch que causam vazamentos de memória, bugs ocultos ou comportamento inadequado do aplicativo. Vamos ver os cinco problemas mais comuns no desenvolvimento mobile.
Catch vazio é uma das piores práticas. A exceção é engolida, o aplicativo continua operando em um estado incorreto e o desenvolvedor nunca fica sabendo do problema. Sempre pelo menos registre a exceção. Em Kotlin use catch(e: Exception) { Log.e(...) }, em Swift — catch { print($0) }. Em Dart, um catch minimamente aceitável deve chamar debugPrint ou escrever no Crashlytics.
Capturar todas as exceções via catch (Exception e) sem distinguir por tipos esconde erros inesperados — NullPointerException, OutOfMemoryError, StackOverflowError. Capture apenas os tipos que você espera e pode tratar. Para todo o resto, permita a propagação para cima. No desenvolvimento mobile, catch específicos para IOException, TimeoutException, AuthException dão mensagens mais significativas ao usuário.
Recursos — arquivos, sockets, cursores de BD, animações — devem ser liberados em finally ou em um bloco use (AutoCloseable). Desenvolvedores frequentemente esquecem de fechar os recursos quando ocorre uma exceção, causando vazamentos. Em Kotlin use .use { } para recursos Closeable, em Swift — defer { }, em Dart — await using do pacote async. O bloco finally garante a liberação mesmo quando uma exceção é lançada dentro de catch.
Em código assíncrono, try-catch não captura exceções de outras threads. Em Kotlin Coroutines use CoroutineExceptionHandler ou SupervisorJob. Em Swift async/await — do-catch dentro de Task. Em Flutter — runZonedGuarded para captura global. Ignorar esta regra é a causa de crashes difíceis de reproduzir em produção.
O tratamento de exceções não deve bloquear a interface do usuário indefinidamente. Mostre ao usuário uma mensagem específica e dê a ele a oportunidade de tentar novamente. Uma Snackbar com botão Retry em Kotlin/Compose, UIAlertController com ação em Swift, SnackBar com ação em Flutter — uma UX minimamente suficiente para erros de rede ou servidor. Evite diálogos genéricos “Ocorreu um erro” sem opções de recuperação.
Perguntas frequentes
Try-catch usa exceções e desenrolamento de pilha para tratamento de erros, o que pode ser custoso em desempenho ao lidar com muitos erros. Result Type é um tipo contêiner (Success ou Failure) que é tratado via pattern matching sem desenrolamento de pilha, sendo mais eficiente para erros esperados.
Finally é obrigatório se o bloco try abre recursos (arquivos, sockets, cursores) que precisam ser fechados. Se nenhum recurso for aberto, finally não é necessário. Em linguagens modernas, use AutoCloseable/use/defer para fechamento automático de recursos sem finally. O bloco use em Kotlin e Swift substitui finally para objetos Closeable.
No fluxo normal (sem exceções), try-catch praticamente não afeta o desempenho — a JVM e o compilador Swift otimizam este caso. Mas quando uma exceção é lançada, ocorre o desenrolamento da pilha, que pode levar 10–100 μs dependendo da profundidade da pilha. Não use exceções para controle de fluxo — isso é um antipadrão.
Em corrotinas, use try-catch dentro de coroutineScope ou CoroutineExceptionHandler para captura global. SupervisorJob impede que a corrotina pai seja cancelada quando uma corrotina filha falha. Para launch use CoroutineExceptionHandler, para async — try-catch em torno de await().
Vários catch são preferíveis: o código é lido linearmente, cada bloco trata um tipo de exceção. Um único catch com if-else é mais difícil de manter e é fácil perder um novo tipo de exceção. Em Swift, vários catch são obrigatórios para tratamento exaustivo de enum Error, em Kotlin não há restrições, mas a melhor prática é um catch separado por tipo.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também