Table of Contents
How to do exception handling in Kotlin?
In programming, sometimes unexpected things can happen, like trying to divide a number by zero or reading a file that doesn’t exist. Exception handling is like having a safety net to catch these unexpected situations. In Kotlin, we use try
, catch
, and finally
to handle these situations.
fun divide(a: Int, b: Int): Int {
return try {
// Code that might cause an issue (e.g., division by zero)
a / b
} catch (e: ArithmeticException) {
// Code to handle the issue (e.g., print an error message)
-1
} finally {
// Code that always runs, whether there's an issue or not
}
}
fun main() {
// Calling the function
val result = divide(10, 2)
// Printing the result
println("Result: $result")
}
KotlinExplanation:
try
block: It contains the code that might cause an issue, like dividing by zero (a / b
).catch
block: If an issue occurs, this block is executed. It contains code to handle the issue, such as printing an error message (-1
is returned as an example).finally
block: This block always runs, whether there’s an issue or not. It is used for code that should execute regardless of what happens.
Examples of Simple Exception Handling
Now, let’s see another example where we ask the user for input and handle the case when the input is not a number.
fun getUserInput(): Int {
return try {
// Attempting to read a number from the user
print("Enter a number: ")
readLine()!!.toInt()
} catch (e: NumberFormatException) {
// Handling the case when the input is not a number
println("Error: Not a valid number!")
}
}
fun main() {
// Calling the function to get user input
val userInput = getUserInput()
// Printing the user's input or a default value in case of an error
println("User entered: $userInput")
}
KotlinExplanation:
- The
try
block attempts to read a number from the user usingreadLine()!!.toInt()
. - If the input is not a number (causing a
NumberFormatException
), thecatch
block is executed. It prints an error message and returns a default value (0 in this case). - In the
main
function, we call thegetUserInput
function, and the user’s input (or the default value in case of an error) is printed.
This is a simplified way of handling unexpected situations in your code. The try
block contains the risky code, the catch
block deals with issues, and the finally
block ensures that some code always runs, providing a safety net for your program.
In Kotlin, exceptions are broadly categorized into two types: Checked and Unchecked Exceptions.
Checked Exceptions:
- These are exceptions that the compiler checks you to handle explicitly.
- They are usually related to external factors beyond the program’s control, such as file I/O operations or network connections.
- You must either catch these exceptions using a
catch
block or declare that your function/method throws these exceptions using thethrows
clause.
fun readFile() {
try {
// Code that might throw a checked exception
} catch (e: IOException) {
// Handle the IOException
}
}
KotlinUnchecked Exceptions:
- These are exceptions that the compiler does not force you to handle explicitly.
- They typically represent programming errors and are subclasses of
RuntimeException
. - Unchecked exceptions are not anticipated during the compilation process, and the compiler doesn’t enforce handling them.
fun divide(a: Int, b: Int): Int {
// Code that might throw an unchecked exception (e.g., ArithmeticException)
return a / b
}
Kotlin2. Throwable Hierarchy
The Throwable class is at the top of the exception hierarchy in Kotlin. It has two main subclasses: Error and Exception.
- Error:
- Represents serious issues that are usually beyond the control of the program.
- Errors are not meant to be caught or handled by regular application code.
- Examples include
OutOfMemoryError
andStackOverflowError
.
- Exception:
- Represents issues that can be anticipated and handled by your program.
- Exceptions are further divided into Checked and Unchecked Exceptions.
- Examples include
IOException
,NullPointerException
, andArithmeticException
.
Throwable Hierarchy:
Throwable
├── Error
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception
├── RuntimeException (Unchecked)
│ ├── ArithmeticException
│ ├── NullPointerException
│ └── ...
└── Other Exceptions (Checked)
├── IOException
├── SQLException
└── ...
KotlinUnderstanding the Throwable hierarchy helps you categorize and handle exceptions effectively in your Kotlin code. It allows you to differentiate between critical errors that might require special handling (Error) and exceptions that can be managed within your application logic (Exception)
- RuntimeException (Unchecked): These are unexpected issues that might occur while the robot is doing its thing but aren’t explicitly planned for.
- ArithmeticException: If the robot tries to divide by zero, it faces an arithmetic problem.
- NullPointerException: If the robot is asked to find something that doesn’t exist, it’s like asking it to look in an empty box.
- Other Exceptions (Checked): These are issues that we know might happen, and we plan for them in advance.
- IOException: Think of the robot working with external devices (like reading from a USB drive) and the drive suddenly disconnects.
- SQLException: If the robot is dealing with a database and can’t find the information it needs.
Creating Custom Exception Classes
In Kotlin, you can create your own custom exceptions by defining classes that inherit from the Exception
class or its subclasses. This allows you to represent specific error scenarios in your application.
In our banking application, we can define custom exceptions to represent various scenarios:
// Custom exception for insufficient funds
class InsufficientFundsException(accountNumber: String, requiredAmount: Double, availableBalance: Double) :
Exception("Account $accountNumber does not have sufficient funds. Required: $requiredAmount, Available: $availableBalance")
// Custom exception for invalid account number
class InvalidAccountNumberException(accountNumber: String) :
RuntimeException("Invalid account number: $accountNumber")
// Custom exception for transaction limit exceeded
class TransactionLimitExceededException(limit: Double) :
RuntimeException("Transaction limit exceeded. Maximum allowed: $limit")
KotlinIn the above example:
InsufficientFundsException
represents a scenario where an account does not have enough funds for a transaction.InvalidAccountNumberException
represents an invalid account number scenario.TransactionLimitExceededException
represents a case where a transaction exceeds a predefined limit.
Throwing and Handling Custom Exceptions
Now, let’s use these custom exceptions in a banking service:
class BankingService {
fun performTransaction(accountNumber: String, amount: Double) {
try {
validateAccountNumber(accountNumber)
validateTransactionLimit(amount)
// Perform the transaction logic
// (For simplicity, we assume the transaction is successful in this example)
println("Transaction successful for account $accountNumber. Amount: $amount")
} catch (e: InsufficientFundsException) {
// Handle insufficient funds scenario
println("Transaction failed: ${e.message}")
} catch (e: InvalidAccountNumberException) {
// Handle invalid account number scenario
println("Transaction failed: ${e.message}")
} catch (e: TransactionLimitExceededException) {
// Handle transaction limit exceeded scenario
println("Transaction failed: ${e.message}")
}
}
private fun validateAccountNumber(accountNumber: String) {
if (accountNumber.length != 10) {
throw InvalidAccountNumberException(accountNumber)
}
// Additional validation logic
}
private fun validateTransactionLimit(amount: Double) {
val transactionLimit = 1000.0
if (amount > transactionLimit) {
throw TransactionLimitExceededException(transactionLimit)
}
// Additional validation logic
}
}
fun main() {
// Example usage of the banking service
val bankingService = BankingService()
bankingService.performTransaction("1234567890", 500.0) // Valid transaction
bankingService.performTransaction("9876543210", 1500.0) // Exceeds transaction limit
}
KotlinIn this example:
- The
BankingService
class has a methodperformTransaction
that performs a banking transaction. - Custom exceptions are thrown based on different scenarios such as insufficient funds, invalid account number, or exceeding the transaction limit.
- The
main
function demonstrates the usage of theBankingService
with two different transactions, one valid and one exceeding the transaction limit.
This example provides a more realistic scenario where custom exceptions are created to handle specific issues that may arise in a banking application. The try-catch
blocks in the performTransaction
method handle these custom exceptions gracefully.
Kotlin Exception Handling in File I/O
Note:
“I/O” stands for Input/Output. In the context of computer systems and programming, I/O refers to the communication and data transfer between a program and external devices, such as storage devices, network devices, and user input/output devices.
Kotlin provides a rich set of functions and classes for working with files. When dealing with File I/O, exceptions may occur due to various reasons such as file not found, permission issues, or I/O errors. Here’s an overview of how you can handle exceptions in File I/O:
import java.io.BufferedReader
import java.io.FileReader
import java.io.IOException
fun readFile(filename: String) {
try {
val reader = BufferedReader(FileReader(filename))
var line: String?
while (reader.readLine().also { line = it } != null) {
// Process each line of the file
println(line)
}
} catch (e: IOException) {
// Handle file I/O exception
println("Error reading file: ${e.message}")
}
}
KotlinIn this example:
- The
try
block contains the code that might throw exceptions during file reading. - The
catch
block catches anIOException
(a common exception for I/O operations) and handles it appropriately. - You can log the exception, print an error message, or take other corrective actions.
Kotlin Network Operations and Exception Handling
Network operations, such as making HTTP requests, can result in exceptions due to connectivity issues, timeouts, or other network-related problems. Here’s an example of exception handling in network operations:
import java.net.URL
import java.io.IOException
fun fetchDataFromUrl(urlString: String) {
try {
val url = URL(urlString)
val connection = url.openConnection()
// Perform network operations
// (Implementation details depend on the specific use case)
} catch (e: IOException) {
// Handle network-related exception
println("Error fetching data: ${e.message}")
}
}
KotlinIn this case:
- The
try
block contains the code for establishing a connection and performing network operations. - The
catch
block catches anIOException
and handles it, which can include logging the error or displaying a user-friendly message.
Kotlin Database Interactions Exception Handling
When interacting with databases, exceptions may occur due to issues like connection problems, SQL errors, or database server unavailability. Here’s an example of exception handling in database interactions:
import java.sql.DriverManager
import java.sql.SQLException
fun performDatabaseOperation() {
try {
val connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "user", "password")
// Perform database operations
// (Implementation details depend on the specific use case)
} catch (e: SQLException) {
// Handle database-related exception
println("Database error: ${e.message}")
}
}
KotlinHow do Coroutines handle exceptions in Kotlin?
Understanding Coroutines
Kotlin Coroutines have revolutionized asynchronous programming, offering a concise and expressive way to write asynchronous code. We’ll focus on an essential aspect of robust asynchronous programming: Exception Handling. Buckle up as we explore how to handle errors gracefully and maintain structured concurrency in your coroutine-based applications.
Structured Concurrency
One of the key strengths of Kotlin Coroutines is structured concurrency. Unlike traditional threading models, structured concurrency ensures that coroutines are tied to a specific scope, making it easier to manage their lifecycle and handle exceptions. Let’s illustrate this concept with a simple example:
import kotlinx.coroutines.*
fun main() = runBlocking {
try {
launch {
// Coroutine 1
delay(100)
println("Task 1 completed")
}
launch {
// Coroutine 2
delay(50)
throw IllegalArgumentException("Oops! Something went wrong in Task 2")
}
} catch (e: Exception) {
// Handle exceptions here
println("Caught an exception: ${e.message}")
}
}
KotlinExplanation:
runBlocking
: This function creates a new coroutine and blocks the current thread until its completion. It’s often used in main functions to launch coroutines.launch
: Launches a new coroutine in the specified scope. In this example, two coroutines are launched concurrently.delay
: Suspends the coroutine for a specified time. Used here to simulate tasks taking different durations.throw IllegalArgumentException
: Simulates an exception occurring in one of the coroutines.catch (e: Exception)
: Catches any exceptions thrown within therunBlocking
scope.
Asynchronous Operations
Now, let’s explore combining asynchronous operations using async
and await
. This allows you to run multiple asynchronous tasks concurrently and await their results. Here’s an example:
import kotlinx.coroutines.*
suspend fun fetchDataAsync(): String {
delay(100)
return "Fetched data"
}
suspend fun processAsyncData(data: String): String {
delay(50)
return "Processed: $data"
}
fun main() = runBlocking {
try {
val result = coroutineScope {
val dataJob = async { fetchDataAsync() }
val processedDataJob = async { processAsyncData(dataJob.await()) }
processedDataJob.await()
}
println("Result: $result")
} catch (e: Exception) {
println("Caught an exception: ${e.message}")
}
}
KotlinExplanation:
async
: Creates a coroutine that performs asynchronous operations. It returns aDeferred
object, allowing you to asynchronously retrieve the result.await
: Suspends the coroutine until the result is ready, allowing you to combine multiple asynchronous operations.
Kotlin Functions
Advanced Functions | – Higher-Order Functions – Lambda Functions – Extension Functions – Infix Functions- Generic Functions |
Functions | – Simple Functions – Function Parameters – Function Return Types – Function Scope |
Functional Programming Functions | – Immutability and Pure Functions- Function Composition – Functional Operators |
Feel free to follow and join my email list at no cost. Don’t miss out — sign up now!
Please check out my earlier blog post for more insights. Happy reading!
The Innovative Fundamentals of Android App Development in 2023