Kotlin Exceptions
Exception handling is a crucial aspect of writing robust applications. In this lesson, we'll explore how Kotlin handles errors through its exception mechanism, which allows your program to respond gracefully when something unexpected happens during execution.
What Are Exceptions?
Exceptions are special events that disrupt the normal flow of a program when an error occurs. Instead of allowing your program to crash, exceptions provide a way to detect problems at runtime and handle them appropriately.
In Kotlin, exceptions are represented by instances of classes that inherit from the Throwable
class. The two main subclasses are:
Error
: Represents serious problems that a reasonable application shouldn't try to catchException
: Represents conditions that a reasonable application might want to catch
Throwing Exceptions
You can throw an exception in Kotlin using the throw
keyword:
fun validateAge(age: Int) {
if (age < 0) {
throw IllegalArgumentException("Age cannot be negative")
}
println("Age is valid: $age")
}
// Example usage:
fun main() {
try {
validateAge(25) // Works fine
validateAge(-5) // This will throw an exception
} catch (e: IllegalArgumentException) {
println("Caught an exception: ${e.message}")
}
}
Output:
Age is valid: 25
Caught an exception: Age cannot be negative
Basic Exception Handling with try-catch
The primary mechanism for exception handling in Kotlin is the try-catch
block:
fun main() {
try {
// Code that might throw an exception
val result = 10 / 0
println("This line will never execute")
} catch (e: ArithmeticException) {
// Code to handle the exception
println("Cannot divide by zero: ${e.message}")
}
// Program continues execution
println("Program continues running")
}
Output:
Cannot divide by zero: / by zero
Program continues running
Multiple catch Blocks
You can handle different types of exceptions with multiple catch blocks:
fun main() {
try {
val numbers = listOf(1, 2, 3)
println(numbers[5]) // This will throw IndexOutOfBoundsException
val result = 10 / 0 // This would throw ArithmeticException but won't be reached
} catch (e: IndexOutOfBoundsException) {
println("Index error: ${e.message}")
} catch (e: ArithmeticException) {
println("Arithmetic error: ${e.message}")
} catch (e: Exception) {
// Catch any other exceptions that weren't caught above
println("Some other exception: ${e.message}")
}
}
Output:
Index error: Index 5 out of bounds for length 3
The finally Block
The finally
block always executes regardless of whether an exception was thrown or caught:
fun readFile(filename: String) {
var file: java.io.FileReader? = null
try {
file = java.io.FileReader(filename)
// Process file content
println("Reading file: $filename")
// This will throw an exception if the file doesn't exist
} catch (e: java.io.FileNotFoundException) {
println("File not found: ${e.message}")
} finally {
// This code always runs, whether exception occurs or not
println("Closing file resources")
file?.close()
}
}
fun main() {
readFile("existing-file.txt") // Assume this file exists
readFile("non-existent-file.txt") // This file doesn't exist
}
Output (if first file exists):
Reading file: existing-file.txt
Closing file resources
File not found: non-existent-file.txt (No such file or directory)
Closing file resources
try-catch as an Expression
In Kotlin, try-catch
can be used as an expression that returns a value:
fun parseInteger(str: String): Int {
val result = try {
str.toInt()
} catch (e: NumberFormatException) {
0 // Default value when parsing fails
}
return result
}
fun main() {
println(parseInteger("42")) // Success case
println(parseInteger("4.2")) // Failure case
println(parseInteger("hello")) // Failure case
}
Output:
42
0
0
Creating Custom Exceptions
You can create your own exception types by extending the Exception
class or any of its subclasses:
class InsufficientFundsException(message: String) : Exception(message)
class BankAccount(private var balance: Double) {
fun withdraw(amount: Double) {
if (amount > balance) {
throw InsufficientFundsException("Cannot withdraw $amount, balance is $balance")
}
balance -= amount
println("Withdrew $amount. New balance: $balance")
}
}
fun main() {
val account = BankAccount(100.0)
try {
account.withdraw(50.0) // Works fine
account.withdraw(75.0) // Throws InsufficientFundsException
} catch (e: InsufficientFundsException) {
println("Transaction failed: ${e.message}")
}
}
Output:
Withdrew 50.0. New balance: 50.0
Transaction failed: Cannot withdraw 75.0, balance is 50.0
Checked vs. Unchecked Exceptions in Kotlin
Unlike Java, Kotlin doesn't have checked exceptions. All exceptions in Kotlin are unchecked, meaning you're not forced to catch or declare any exceptions. This design decision was made to avoid boilerplate code.
try-with-resources in Kotlin (use function)
Kotlin doesn't have a built-in try-with-resources
statement like Java, but instead provides the use
extension function to automatically close resources:
import java.io.BufferedReader
import java.io.FileReader
fun readFirstLine(path: String): String {
BufferedReader(FileReader(path)).use { reader ->
return reader.readLine() ?: "File was empty"
}
// The reader is automatically closed when the block exits
}
fun main() {
try {
val firstLine = readFirstLine("example.txt")
println("First line: $firstLine")
} catch (e: java.io.IOException) {
println("Error reading file: ${e.message}")
}
}
When to Use Exceptions
Exceptions should be used for exceptional conditions, not for normal flow control:
- Good use case: A file you're trying to read doesn't exist
- Bad use case: Using exceptions to determine if an element is in a list (use normal control flow instead)
Real-World Example: Form Validation
Here's a real-world example of using exceptions for validating user input in a form:
// Custom exceptions
class ValidationException(message: String) : Exception(message)
class EmptyFieldException(fieldName: String) : ValidationException("The $fieldName field cannot be empty")
class InvalidEmailException(email: String) : ValidationException("'$email' is not a valid email address")
class PasswordTooWeakException : ValidationException("Password must contain at least 8 characters, including a number and a special character")
class UserRegistrationForm(
val username: String,
val email: String,
val password: String
) {
fun validate() {
validateUsername()
validateEmail()
validatePassword()
println("All fields are valid!")
}
private fun validateUsername() {
if (username.isBlank()) {
throw EmptyFieldException("username")
}
}
private fun validateEmail() {
if (email.isBlank()) {
throw EmptyFieldException("email")
}
// Simple email validation
if (!email.contains("@") || !email.contains(".")) {
throw InvalidEmailException(email)
}
}
private fun validatePassword() {
if (password.length < 8 || !password.any { it.isDigit() } || !password.any { !it.isLetterOrDigit() }) {
throw PasswordTooWeakException()
}
}
}
fun main() {
val forms = listOf(
UserRegistrationForm("john_doe", "[email protected]", "p@ssw0rd"),
UserRegistrationForm("", "[email protected]", "password123!"),
UserRegistrationForm("jane_doe", "not-an-email", "strongpass1!"),
UserRegistrationForm("bob_smith", "[email protected]", "weak")
)
for ((index, form) in forms.withIndex()) {
println("\nValidating form #${index + 1}...")
try {
form.validate()
println("Registration successful for user: ${form.username}")
} catch (e: ValidationException) {
println("Registration failed: ${e.message}")
}
}
}
Output:
Validating form #1...
All fields are valid!
Registration successful for user: john_doe
Validating form #2...
Registration failed: The username field cannot be empty
Validating form #3...
Registration failed: 'not-an-email' is not a valid email address
Validating form #4...
Registration failed: Password must contain at least 8 characters, including a number and a special character
Summary
In this lesson, you've learned:
- How to throw and catch exceptions in Kotlin
- How to use try-catch blocks for error handling
- The importance of the finally block for resource cleanup
- How to create custom exception classes
- Using try-catch as an expression
- The
use
function for resource management - Best practices for exception handling in real-world applications
Exception handling is essential for creating resilient applications that can gracefully handle unexpected errors. By mastering exceptions in Kotlin, you'll be able to write code that's more robust and user-friendly.
Exercises
- Create a calculator program that handles various exceptions like division by zero and invalid operations.
- Write a function that reads a file and handles common IO exceptions.
- Create a custom exception hierarchy for a banking application that includes exceptions for insufficient funds, account not found, and daily withdrawal limit exceeded.
- Extend the form validation example to include additional validations like phone number format and age restrictions.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)