Try-Catch: essence, exception handling construction and how it works in mobile development

Author: IT Sectr Published: 2026-05-25 Reading time: 9 min

Try-Catch is an exception handling construct that allows executing potentially dangerous code in a protected block and correctly handling errors without crashing the program. The try block contains code that may throw an exception, catch intercepts it and executes recovery logic. According to Apple Swift Documentation (2026), the finally block executes regardless of whether an exception was thrown or not, guaranteeing resource release.

Key Takeaways

  • Try-Catch is an exception handling construct that prevents program crashes when errors occur
  • The try block contains code that may throw an exception — execution stops at the first error
  • The catch block catches an exception of the specified type and executes handling or recovery logic
  • The finally block always executes after try/catch to release resources, for example closing files
  • Nested try-catch allows handling errors at different abstraction levels within a single function

What is Try-Catch?

Try-Catch is a fundamental structural exception handling construct present in most modern programming languages. It consists of three blocks: try (attempting to execute dangerous code), catch (intercepting and handling the exception), and optional finally (finalization). The idea of the construct is to separate business logic from error handling logic, making code more readable and predictable.

The concept was first implemented in C++ as try/catch, then adopted by Java, C#, Swift, Kotlin, Dart, Python, JavaScript, and other languages. Each language adds its own features: in Swift the catch block must be exhaustive, in Kotlin try-catch can be an expression, in Dart finally is required for stream resources. Despite the differences, the basic principle is the same: an error is handled as close to the point of occurrence as possible, not globally.

Using Try-Catch is especially important in mobile development, where external factors — network loss, incorrect server response, insufficient memory — occur constantly. Proper exception handling prevents app crashes and ensures correct UX: the user sees an error message instead of a sudden app closure. According to Google Android Kotlin Style Guide (2026), every function that may throw an exception should either handle it via try-catch or declare throws in its signature.

How Try-Catch Works

The try-catch execution mechanism is based on stack unwinding. When an exception is thrown inside the try block via the throw operator (or as a result of a system error), the normal execution flow is immediately interrupted. Execution moves up one level in the call stack searching for a suitable catch block. Modern languages look for a catch with a type matching the thrown exception type using a type matching mechanism.

If a matching catch is found, its body executes, after which execution continues after the entire try-catch-finally construct. If no catch is found, the exception propagates further up the stack and may be handled at a higher level — up to a global handler, which in a mobile app shows the user an error dialog. If the exception is not handled anywhere, the application crashes. This is why correct handling of all possible exception types is critical for application stability.

Basic Construct Syntax

kotlin
fun readUserData(): User {
    return try {
        val response = api.fetchUser()
        parseUser(response)
    } catch (e: IOException) {
        logError("Network error", e)
        throw AppException("Failed to load data")
    } catch (e: JsonParseException) {
        logError("Parse error", e)
        return User.default()
    } finally {
        closeLoadingIndicator()
    }
}

The code first attempts to make an API request and parse the response. If an IOException (network problem) occurs, the exception is logged and rethrown as AppException. If a JsonParseException occurs — a default user is returned. The finally block guarantees hiding the loading indicator, preventing UI component leaks on the screen.

Try-Catch in Swift

In Swift, error handling is implemented through the Error protocol (formerly ErrorType). Any type conforming to Error can be thrown using the throw operator. A function that can throw an error is marked with the throws keyword in its signature. Calling such a function requires the try prefix (for explicit try-catch), try? (optional result), or try! (forced execution without error handling).

swift
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("Network " + error.localizedDescription)
} catch {
    showGenericError()
}

The enum NetworkError implements the Error protocol, defining three cases: noConnection, serverError with a code, and timeout. The fetchUser function is marked throws: it first checks the connection, then performs the request and parsing. In the do-catch block, three catch clauses handle different scenarios: the specific noConnection case, the general NetworkError type, and all other errors. This allows showing the user different messages depending on the problem type.

Try-Catch in Kotlin

Kotlin inherited try-catch-finally from Java but added an important difference: in Kotlin, try-catch is an expression, not a statement. This means the result of a try block or catch block can be assigned to a variable. The last expression in the try block becomes the result on success, the last expression in the catch block — on error. If the error is not handled by any catch, the exception propagates up the stack.

kotlin
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("Network ", e)
        Result.Error(e)
    }
}

In the example, sealed class Result wraps a successful response or an error. The loadData function uses try-catch as an expression: on success it returns Result.Success, on HttpException or IOException — Result.Error with logging. This approach allows the caller to handle errors without exceptions — via a when expression on the Result type. This is especially convenient in Jetpack Compose for displaying different UI states (Loading, Success, Error) through StateFlow and collectAsState.

Try-Catch in Dart and Flutter

Dart supports try-catch-finally with syntax similar to Java, but with the addition of an on-clause for filtering by exception type without specifying a variable. This is convenient when the exception itself is not needed — only the fact of its type matters. Dart also supports a catch block with two parameters: the exception object and StackTrace, which is useful for logging the full call chain.

dart
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 is caught with both the exception object and StackTrace for detailed logging, then rethrown as AppException. FormatException is caught without a variable — knowing the response format is incorrect is enough. The finally block guarantees closing the HttpClient, preventing socket leaks. In Flutter, this approach is especially important for Widget testing, where unhandled exceptions in State.initState cause the entire test session to crash.

Common Mistakes When Using Try-Catch

Even experienced developers make mistakes when working with try-catch that lead to memory leaks, hidden bugs, or inappropriate application behavior. Let's look at the five most common problems in mobile development.

Empty catch block

Empty catch is one of the worst practices. The exception is swallowed, the application continues operating in an incorrect state, and the developer never learns about the problem. Always at least log the exception. In Kotlin use catch(e: Exception) { Log.e(...) }, in Swift — catch { print($0) }. In Dart, a minimally acceptable catch should call debugPrint or write to Crashlytics.

Too broad catch

Catching all exceptions via catch (Exception e) without type distinction hides unexpected errors — NullPointerException, OutOfMemoryError, StackOverflowError. Catch only the types you expect and can handle. For everything else, allow propagation upward. In mobile development, specific catch blocks for IOException, TimeoutException, AuthException give more meaningful messages to the user.

Ignoring finally

Resources — files, sockets, database cursors, animations — must be released in finally or in a use-block (AutoCloseable). Developers often forget to close resources when an exception occurs, leading to leaks. In Kotlin use .use { } for Closeable resources, in Swift — defer { }, in Dart — await using from the async package. The finally block guarantees release even when an exception is thrown inside catch.

Exceptions in threads and coroutines

In asynchronous code, try-catch does not catch exceptions from other threads. In Kotlin Coroutines use CoroutineExceptionHandler or SupervisorJob. In Swift async/await — do-catch inside Task. In Flutter — runZonedGuarded for global interception. Ignoring this rule is the cause of hard-to-reproduce crashes in production.

UI blocking on exception

Exception handling should not block the user interface indefinitely. Show the user a specific message and give them the opportunity to retry the operation. Snackbar with a Retry button in Kotlin/Compose, UIAlertController with action in Swift, SnackBar with action in Flutter — a minimally sufficient UX for network or server errors. Avoid generic “An error occurred” dialogs without recovery options.

Frequently Asked Questions

How is try-catch different from Result Type?

Try-catch uses exceptions and stack unwinding for error handling, which can be costly in performance when dealing with many errors. Result Type is a container type (Success or Failure) that is handled via pattern matching without stack unwinding, making it more efficient for expected errors.

Do I need to use finally in every try-catch?

Finally is required if the try block opens resources (files, sockets, cursors) that need to be closed. If no resources are opened, finally is not needed. In modern languages, use AutoCloseable/use/defer for automatic resource closing without finally. The use-block in Kotlin and Swift replaces finally for Closeable objects.

Can try-catch be slow?

In the normal flow (without exceptions), try-catch has practically no performance impact — the JVM and Swift compiler optimize this case. However, when an exception is thrown, stack unwinding occurs, which can take 10–100 μs depending on stack depth. Do not use exceptions for flow control — this is an anti-pattern.

How to handle errors in Kotlin Coroutines?

In coroutines, use try-catch inside coroutineScope or CoroutineExceptionHandler for global interception. SupervisorJob prevents the parent coroutine from being cancelled when a child coroutine fails. For launch use CoroutineExceptionHandler, for async — try-catch around await().

Which is better: multiple catches or a single catch with if-else?

Multiple catches are preferable: the code reads linearly, each block handles one exception type. A single catch with if-else is harder to maintain, and it’s easy to miss a new exception type. In Swift, multiple catches are mandatory for exhaustive enum Error handling, in Kotlin there are no restrictions, but best practice is a separate catch per type.

Summary

  • Try-Catch is an exception handling construct with try, catch, and optional finally blocks for guaranteed resource release
  • How it works — stack unwinding when an exception is thrown, searching for a matching catch by error type
  • In Swift — do-catch with enum Error, try? for optional result, and try! for guaranteed success
  • In Kotlin — try-catch is an expression whose result can be assigned to a variable, convenient with sealed class Result
  • In Dart — supports on-clause for filtering by type without a variable and finally for closing HttpClient
  • Common mistakes: empty catch, too broad catch, ignoring finally, and lack of handling in coroutines
  • Use try-catch for unexpected errors, Result Type — for expected failure scenarios

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also