Kotlin: Getting to knows with Exceptions
Problems: Exception Handling
Exceptions were something that occurred on daily basis. In Java, they can eliminate the problems we might find on a specific operation for every programming language (back then), see this example:
Writing the above handling seems too much error-prone if there’s no proper linter (pipeline) and a strict code review process.
An alternative: Throw Exception
Java then introduce a keyword called throws
, as a compiler checks that we need to check or handle it because this unique concept of checked exceptions would solve the error-prone of error-handling using a manual if condition with try/catch
but that's not the case exception designed in Kotlin.
Checked exceptions are something to guard our codebase so we are recommended to catch that particular method when invoked, e.g:
The above was an example of a checked exception allowing us to have the compiler checks with try/catch
for example:
If we didn’t handle the checked exceptions on method
.read
it'll show a compiler error checks
This code still compiled & runs, but might produce
NPE
in the runtime
The above approach was still something tedious to write especially the anti-pattern for this simple exception
We’ll not cover up the gritty details about the above anti-pattern but you can see here for details:
TL;DR: The anti-pattern stated that we should catch only the relevant exception so we know for sure what to handle, instead of catch the general class
Exception
that will throw any possible (almost everything) that (un)intended as well
Multicatch exist!
Prior to Java 7, this is not possible, onwards now we can write a simple “or” |
for the exception, see here for details:
Let’s see the sneak-peek of what it looks like both in Java & Kotlin for the multi-catch exception:
Well, in Java we had what we called a multi-catch exception using a simple |
symbol and voila, it's more readable but how about in Kotlin?
Unfortunately the Kotlin by design it’s not desired to have a multi-catch exception as this ticket is shown:
https://youtrack.jetbrains.com/issue/KT-7128 (still opened from 6yr ago)
And we’re ended up abusing every possible exception (see below screenshot)
Yes the above is our codebase approach back then :) when handling the TUS protocol because its variety of possible exception thrown inside the
try
statement :(
There’s a follow-up statement regarding the multi-catch in the following resources that recommended to watch on the weekends
There’s also a discussion on kotlinlang.slack.com regarding the multicatch
Later, we modify our code to be more readable with an alternative of multi-catch exception, and inspired by the community to have this extension instead:
What are the problems anyway?
We solve most of our exception problems back then with the above nice little extension for a while, until we use a heavy lambda action & coroutines (and yes we use Streams, Executors & Threads in our Java code as well).
Psssttt don’t tell anyone, we also invested heavily on building an interopability like
suspend
function to be called in Java :)
Our Kotlin codes:
Also, many more like making our Java method helper become deprecated and called the Kotlin code inside it. Interoperability in Kotlin comes at a price such as the above condition for suspend
function and many annotations we used such as JvmOverloads
, JvmName
, JvmStatic
, & etc
/**
*
* @deprecated will be removed at vx.y.z
*
**/
@Deprecated
public static String doSomething() {
return OurKotlinCodeKt.doSomething();}
Why the exception is not mixed well in the lambda? Let’s see the following examples:
The above code inside .forEach
might be problems when there is0
value in the list and throw the ArithmeticException: / by zero
. That's bad!
Even we modify our code to have an exception like this
But that’s another cumbersome code to write :) also, we’re losing the conciseness of why we using lambda on the first place.
Writing a wrapper for the try catch also convenient but that’s another tech debt to introduce :))) as shown in here from Baeldung
More examples have shown how much boilerplate to write with a lambda function that returns simply checked exception as explained details in this articles here from DZone:
tl;dr a wrapper workaround: https://stackoverflow.com/a/18198349/3763032
So how Kotlin Design its Exception?
The concept is the same with Java checked exception as a return value of functions with an annotation called @Throws
see here from official Kotlin docs
Kotlin also aware of pre-conditions (alternatively in Java we can use Guava) turns out Kotlin has this internally in their SDK
A precondition is a mechanism to throw an exception that we unable to detect at compile time
e.g require(Boolean) {}
:
TL;DR:
Checked Exceptions:
@Throws(NullPointerException::class) fun doSomething()
We need to handle it before compilation otherwise it’ll show a compile-error
Preconditions:
To check runtime or program (or logic) error such as the above snippet using
require(Boolean) {}
Kotlin Contract (compile-time): This is require a separate discussion, but worth to read how Kotlin allowing us to write the extension or method that’s has some kind of linter to fails the compile. See here:
We are recommended to avoid code smells in our Kotlin code as stated by Roman Elizarov here:
Dual-use APIs
You might familiar with this kind of extensions in Kotlin, such as:
Why Kotlin bother to create a different method just for the sake of nullability?
Well, it’s for us (developer) to reduce writing thistry/catch
style:
Alternatively, we can use.toIntOrNull()
and our code now is more (opinionated) readable:
From a list or collection & map has their own extensions of a dual-use APIs for our conveniences e.g: getOrNull
!
The above concept was introduced to leverage the nullability features in Kotlin there’s a supportive statement why null
is not a foe or such-called a billion-dollar mistakes (but a friend), See here:
Also, Kotlin has the power of elvis operator ?:
that really convenient to be used when dealing with nullability as the above example.
You might be curious why Kotlin doesn’t have the ternary operator as well, e.g: (true) ? "yes" : "no"
.
Here’s why the Kotlin team decided not to have a ternary operator like in Java:
Or go to this Kotlin discussions:
Designing the API
For us when writing the code, whether we ships and SDK to be consumed by fellow developers or internally, we need to bear in mind that:
- Use exception for logic errors
- Type-safe results for everything else
- If other than logic error, write a wrapper to convert exceptions to the desired return values
Therefore our caller will be freed to such length from handling the old try/catch
! :(
P.S.:
- Is it a single error condition: success, or failure? Use
null
as a failure and a type-safe result for success - If it a multiple error condition, take a bit of time to write a wrapper (explained in more details later)
We define a sealed class for the type-safe result of ParseException
instead, a simpler failure to return null
, because we (for example) wants to return the errorOfset
.
Why sealed class
? the result of .parse
and .errorOfset
was returning a different data type, therefore we wrap it as a sealed class
instead.
This is might be a rare case, but as our guidelines for the multiple error conditions or a different return values for failure cases
Input or Outputs failures
As previously stated about require(Boolean) {}
this is a good precondition so that the caller side can have a more centralized (self-documenting) code that we need to avoid adding the negative values for it, e.g:
Asynchronous and Coroutines Exceptions
In Kotlin this is by principles is a first-class citizen supported for the coroutineScope
& launch
that indicate cancellation or failures (internally use the checked exceptions)
See full details about exceptions in coroutines here:
There’s a handy extension called .runCatching
especially for coroutine launch
or async
(if you prefer a functional way rather than using try-catch
) and for the Kotlin Flow
we can also be using .catch
operator to handle the exceptions.
E.g for runCatching
from the above link:
viewModelScope.launch {
kotlin.runCatching { repository.getNecessaryData(this) }
.onSuccess { liveData.postValue(ViewState.Success(it)) }
.onFailure { liveData.postValue(ViewState.Error(it)) }
}
Thanks for the correction and feedback Ade Dyas
There’s also a worth mentioning article about the Exception in Kotlin here (the benefits of using runCatching
):
Handling the exception with on how we able to provide default value, a fallback or even recover from an error such as following:
getOrDefault
orgetOrNull
, we can providenull
or default value
fold
, we could mapping or transform the return value into a different data type:
.fold(onSuccess = { success: String -> success.toIntOrNull() }, onFailure = { error: Throwable -> error.message })
Bonus
Agreed with Erik Huizinga statement regarding the powerful .runCatching
and we might ended-up abusing the thrown exception and therefore this article also address the multi-error values or
It’s also recommended to catch up with the Kotlin community on a slack, youtrack & Github issue for examples:
- https://github.com/Kotlin/kotlinx.coroutines/issues/1147 (this is where the coroutines discussion takes places, or we can check for:
github.com/Kotlin/<library>/issues
) - https://twitter.com/kotlin/status/1022114553302331392?s=20 (kotlinlang.slack.com) we can find many Kotlin library here (also: #android, #squarelibraries, #kmm, #compose, & etc)
- If wants to know more about the Kotlin design and enhancement we can star this repo for sure: https://github.com/Kotlin/KEEP
- A curated Kotlin newsletter: http://www.kotlinweekly.net/
Originally published at http://github.com.