Share
Explore

Kotlin Exception Handling

Kotlin Exception Handling Labs

Lab 1: Introduction to Exception Handling

error

Lab 1: Introduction to Exception Handling

Basic try-catch blocks
Common exception types
The exception hierarchy
Simple exception handling patterns

Lab 2: Advanced Exception Handling

Multiple catch blocks
The finally block
Try-with-resources (using and closeable resources)
Exception propagation

Lab 3: Creating and Using Custom Exceptions

Defining custom exception classes
When to create custom exceptions
Adding meaningful information to exceptions
Exception chaining

Lab 4: Functional Exception Handling

Kotlin's Result type
runCatching and other functional approaches
Exception handling with coroutines
Either types or similar patterns

Lab 5: Architectural Exception Handling

Domain-driven exception design
Creating an exception hierarchy for application architecture
Using exceptions to model business rules
Centralizing exception handling
This progression should take students from the basics to more advanced concepts, ending with custom exceptions for architecture as requested.

/**
* Kotlin Lab 1: Introduction to Exception Handling
*
* This lab introduces basic exception handling in Kotlin using try-catch blocks,
* demonstrates handling different exception types, and shows how to access
* exception information.
*/

fun main() {
println("=== Kotlin Exception Handling: Introduction ===\n")

// Basic try-catch
println("1. Basic try-catch:")
try {
val result = 10 / 0
println("This won't be printed because an exception is thrown")
} catch (e: Exception) {
println("Caught an exception: ${e.message}")
println("Exception type: ${e.javaClass.simpleName}\n")
}

// Handling specific exception types
println("2. Handling specific exception types:")
try {
val numbers = listOf(1, 2, 3)
println(numbers[5]) // This will throw IndexOutOfBoundsException
} catch (e: IndexOutOfBoundsException) {
println("Caught IndexOutOfBoundsException: ${e.message}")
} catch (e: Exception) {
println("Caught some other exception: ${e.message}\n")
}

// Using try as an expression
println("3. Using try as an expression:")
val result = try {
val number = "abc".toInt()
"Result: $number" // This won't be assigned to result
} catch (e: NumberFormatException) {
"Conversion failed: ${e.message}" // This will be assigned to result
}
println("$result\n")

// Accessing exception details
println("4. Accessing exception details:")
try {
throw RuntimeException("Something went wrong", IllegalArgumentException("Invalid input"))
} catch (e: RuntimeException) {
println("Message: ${e.message}")
println("Cause: ${e.cause?.javaClass?.simpleName}")
// Print stack trace
println("Stack trace:")
e.stackTrace.take(3).forEach { element ->
println(" at ${element.className}.${element.methodName}(${element.fileName}:${element.lineNumber})")
}
println()
}

// Uncaught exceptions
println("5. Uncaught exceptions:")
handlePotentialProblem()
println("Program completed successfully!")
}

fun handlePotentialProblem() {
try {
riskyOperation()
println("Risky operation completed successfully")
} catch (e: Exception) {
println("Handled an exception from riskyOperation(): ${e.message}\n")
}
}

fun riskyOperation() {
val data = mapOf("a" to 1, "b" to 2)
val value = data["c"] ?: throw NoSuchElementException("Key 'c' not found in the map")
println("Value: $value") // This won't be executed
}

Code Spotlight: Introduction to Exception Handling

This lab introduces the fundamental concepts of exception handling in Kotlin, and here's how each part works:

1. Basic try-catch Structure

try {
val result = 10 / 0
println("This won't be printed because an exception is thrown")
} catch (e: Exception) {
println("Caught an exception: ${e.message}")
println("Exception type: ${e.javaClass.simpleName}\n")
}

The code attempts a division by zero, which causes an ArithmeticException. The Kotlin runtime responds by:
Immediately stopping execution at the point of the exception
Creating an exception object containing details about what went wrong
Finding the nearest matching catch block in the call stack
Transferring control to that catch block with the exception object as e
Inside the catch block, we extract information from the exception:
e.message: The human-readable description ("/ by zero")
e.javaClass.simpleName: The name of the exception class ("ArithmeticException")

2. Multiple Catch Blocks

try {
val numbers = listOf(1, 2, 3)
println(numbers[5]) // This will throw IndexOutOfBoundsException
} catch (e: IndexOutOfBoundsException) {
println("Caught IndexOutOfBoundsException: ${e.message}")
} catch (e: Exception) {
println("Caught some other exception: ${e.message}\n")
}

This demonstrates catching specific exception types:
Kotlin tries to access an element at index 5, but the list only has 3 elements
This triggers an IndexOutOfBoundsException
Kotlin checks the catch blocks in order, matching the first compatible type
Since IndexOutOfBoundsException matches the first block, that's where execution continues
The second catch block (for general Exception) is skipped
This pattern of arranging catch blocks from most specific to most general is a best practice, allowing you to handle different exceptions differently.

3. Try as an Expression

val result = try {
val number = "abc".toInt()
"Result: $number" // This won't be assigned to result
} catch (e: NumberFormatException) {
"Conversion failed: ${e.message}" // This will be assigned to result
}

This demonstrates Kotlin's ability to use try-catch as an expression:
We attempt to convert "abc" to an integer
This fails with a NumberFormatException
The catch block returns a string, which becomes the value of the entire try-catch expression
This value is assigned to result
This pattern is extremely useful for graceful handling of operations that might fail, allowing you to provide fallback values.

4. Detailed Exception Information

try {
throw RuntimeException("Something went wrong", IllegalArgumentException("Invalid input"))
} catch (e: RuntimeException) {
println("Message: ${e.message}")
println("Cause: ${e.cause?.javaClass?.simpleName}")
// Print stack trace
println("Stack trace:")
e.stackTrace.take(3).forEach { element ->
println(" at ${element.className}.${element.methodName}(${element.fileName}:${element.lineNumber})")
}
}

This section shows how to access detailed exception information:
We manually throw a RuntimeException with a custom message
We provide another exception as the "cause" (exception chaining)
In the catch block, we access various properties:
message: The primary error message
cause: The underlying exception that led to this one
stackTrace: The call stack at the point the exception was thrown
Understanding this rich information is crucial for diagnosing problems, especially in complex applications.

5. Exception Propagation

fun handlePotentialProblem() {
try {
riskyOperation()
println("Risky operation completed successfully")
} catch (e: Exception) {
println("Handled an exception from riskyOperation(): ${e.message}\n")
}
}

fun riskyOperation() {
val data = mapOf("a" to 1, "b" to 2)
val value = data["c"] ?: throw NoSuchElementException("Key 'c' not found in the map")
println("Value: $value") // This won't be executed
}

This demonstrates exception propagation across function calls:
The main function calls handlePotentialProblem()
handlePotentialProblem() calls riskyOperation()
riskyOperation() throws a NoSuchElementException
Since riskyOperation() doesn't catch the exception, it propagates up to handlePotentialProblem()
The catch block in handlePotentialProblem() handles it, preventing it from crashing the program
This mechanism allows lower-level functions to signal problems to higher-level functions that have the context to decide how to handle them.

Key Takeaways from Lab 1

Exceptions provide a structured way to handle errors and exceptional conditions
Try-catch blocks allow you to catch and handle exceptions
Specific exception types let you handle different problems differently
Try-catch can be used as an expression, returning a value
Exceptions contain rich information about what went wrong
Exceptions automatically propagate up the call stack until caught

Lab 2: Advanced Exception Handling

/**
* Kotlin Lab 2: Advanced Exception Handling
*
* This lab explores more advanced exception handling techniques including
* finally blocks, using try-with-resources pattern, and rethrow patterns.
*/

import java.io.File
import java.io.FileReader
import java.io.IOException
import kotlin.system.measureTimeMillis

fun main() {
println("=== Kotlin Exception Handling: Advanced Techniques ===\n")

// 1. Finally block - always executes
println("1. Using finally block:")
try {
println("Inside try block")
if (Math.random() > 0.5) {
throw RuntimeException("Random failure")
}
println("Try block completed successfully")
} catch (e: Exception) {
println("Exception caught: ${e.message}")
} finally {
println("Finally block always executes, whether an exception occurred or not")
}
println()

// 2. Using the try-with-resources pattern (with use function)
println("2. Try-with-resources (use function):")
val tempFile = createTempFile()
try {
FileReader(tempFile).use { reader ->
println("File opened successfully")
val content = reader.readText()
println("Read content: $content")
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.