Exceptions
In an ideal world, nothing would ever go wrong with our apps.
In an ideal world, the author of this book would have a full head of hair.
This is not an ideal world.
As with most modern programming languages, when things go wrong, exceptions get raised. We will need to be able to handle those exceptions when they occur, one way or another.
Kotlin’s exception system is very similar to that of Java, except that there are no checked exceptions.
Catching Exceptions
A classic scenario where our apps can fail is with a NullPointerException
.
We will explore nullability in greater detail in an upcoming chapter, as Kotlin takes steps to try to minimize a NullPointerException
. However, you can still wind up crashing if you try accessing null
inappropriately, such as we are doing here:
var thisIsReallyNull: String? = null
println(thisIsReallyNull)
println(thisIsReallyNull!!.length)
This compiles fine, but it crashes at runtime with a NullPointerException
. We will see exactly what ?
and !!
do in the chapter on nullability.
For now, though, let’s focus on the fact that we are getting an exception. In this case, the exception is very artificial, but there are many places where an exception may be expected somewhat. For example, if you try accessing the Internet, there may be all sorts of problems resulting in all sorts of exceptions.
Traditional Try-Catch
Kotlin supports try
and catch
, using syntax very similar to that of Java:
try {
var thisIsReallyNull: String? = null
println(thisIsReallyNull)
println(thisIsReallyNull!!.length)
}
catch (e: Exception) {
println("ERROR: '$e'")
}
To catch an exception, you:
- Wrap the code that might trigger the exception in a block, preceded by
try
, then - Follow that block with a
catch
block, where you indicate what exception you are expecting
Here, we wrap the null
reference in the try
block. If — or, rather, when — that code throws an exception, we will get control in our catch
block, where we can do something.
Between the catch
keyword and the block is a parameter-style declaration in parentheses, indicating:
- What sort of exception we are looking to catch
- What name to give this exception, that we can use inside the code in the
catch
block to examine the exception itself
Here, anything that extends from Exception
would be caught and handled by our catch
block.
Cascading Catches
If you want to handle different types of exceptions differently, you can have multiple catch
blocks:
try {
var thisIsReallyNull: String? = null
println(thisIsReallyNull)
println(thisIsReallyNull!!.length)
}
catch (npe: NullPointerException) {
println("You tried doing something unfortunate with null. Stop that.")
}
catch (e: Exception) {
println("ERROR: '$e'")
}
Here, when the null
reference throws an NullPointerException
, we will execute the first catch
block. Otherwise, if it were to throw some other type of Exception
, we will execute the second catch block.
Note, though, that Kotlin does not support multiple types in a single catch
, the way you can in Java:
try {
// something that might throw multiple types of exceptions
}
catch (ThisException | ThatException ex) {
// handle those two exception types
}
What You Might Catch
The details of what exceptions can be thrown will vary by circumstance. In particular, the Kotlin environment will play a major role here. For example, java.lang.ArithmeticException
is a Java thing that you might catch on Kotlin/JVM. Other Kotlin environments, such as Kotlin/JS, will not know what java.lang.ArithmeticException
is.
Even within an environment, the details may vary. For example, in Java, Exception
is a subclass of Throwable
. An Exception
is something that the Java language designers felt that apps should be able to handle. An Error
, though, is another type of a Throwable
that the language designers considered to be unrecoverable. For example, an OutOfMemoryError
is something that the Java designers thought was something that should result in the death of your process.
Typically, in a catch
block, you identify exceptions that you want to catch because you have specific local ways to recover from it. If you need a true “catch everything” catch
block, in Kotlin/JVM, you would catch a Throwable
, not an Exception
.
A Missed Catch
An exception that is not caught cascades up the “call stack” to whatever the next function is:
fun itsGonnaBlow() {
var thisIsReallyNull: String? = null
println(thisIsReallyNull)
println(thisIsReallyNull!!.length)
}
fun main() {
try {
itsGonnaBlow()
}
catch (npe: NullPointerException) {
println("You tried doing something unfortunate with null. Stop that.")
}
catch (e: Exception) {
println("ERROR: '$e'")
}
}
Here, itsGonnaBlow()
triggers the exception, but we do not have a try
/catch
structure in that function. So, when itsGonnaBlow()
throws the exception, it gets passed to whoever called itsGonnaBlow()
, and so on.
Try as an Expression
Like if
and when
, try
(with its associated catch
blocks) represents an expression, and you can use its result as you would any other expression, such as assigning it to a variable or property.
In the case of if
and when
, the result of the expression is based on which of the branches you take. In the case of try
, the result of the expression is:
- The value of the last expression of the
try
block, if there is no exception raised in that block, or - The value of the last expression of the
catch
block, if one of thecatch
blocks caught an exception
fun lengthifier(nullAllowedButNotReally: String?): Int {
return nullAllowedButNotReally!!.length
}
fun main() {
val result = try {
lengthifier(null)
}
catch (e: Exception) {
-1
}
println(result)
}
Here, we compute the length of a String
in a function, and we wrap that function call in a try
/catch
structure. result
will either be:
- The length of the string, or
-
-1
if we crash, such as with aNullPointerException
In this case, we are trying to get the length of null
, so we get -1
as the result of our try
expression.
Finally, Don’t Forget finally
As with Java, Kotlin also offers a finally
block that you can chain onto the end of your try
/catch
structure. This block’s code will be executed either:
- After the
try
block’s contents, if there are no exceptions thrown by that block, or - After the
catch
block’s contents, if thatcatch
caught an exception raised by thetry
block
Typically, you use the finally
block to ensure that everything is cleaned up from whatever may (or may not) have happened in the try
:
try {
// TODO something that might blow up
}
catch (e: Exception) {
// TODO something to deal with the blow up when it blows up
}
finally {
// TODO clean up afterwards
}
Note that if you use the try
as an expression, the finally
block has no impact on the result of the expression. The result will still be based on the try
result or the catch
result — the finally
code is executed but does not change the result.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.