A Perception of Exception in Kotlin Coroutines
Don’t let them handle you, you handle them!
Exception handling is a very important part of any code execution, and when it comes to Coroutines, it becomes very critical because to understand exception handling for Coroutines, you need to understand their basic way of operation and the concurrency pattern they support. Without knowing it, writing any number of try-catch blocks won’t help at all.
If you want to know about the what, why, and how of coroutines, do read this and this. You won’t be disappointed.
Assuming we have a basic understanding of Coroutines, in this article, we’ll try to simplify and understand how exception handling works for coroutines. We’ll cover the topic by understanding the following in the specified order.
- What is Exception and Cancellation in the context of Coroutines?
- How Exceptions propagate in Coroutines hierarchy?
- How to handle Exceptions in Coroutines?
- What is Exception aggregation?
What Is Exception and Cancellation in the Context of Coroutines?
This can be a little tricky and confusing but we need to be very clear on this before we process anything else. In simple terms:
An exception is something that occurred unexpectedly and is uncaught or unhandled for a child and its parent coroutine. Uncaught exception in any child coroutine cancels its parent job.
Cancellation is when a coroutine is cancelled with CancellationException
but does not cancel its parent’s job. Coroutine cancellation with CancellationException
is ignored by the parent handler because it is expected behavior for a cancelled coroutine.
Though these are two different behaviors but are also related to each other because Cancellation internally uses CancellationException
to update the parent.
Yes, it's that straightforward. We’ll understand these more as we move forward.
How Exceptions Propagate in Coroutines Hierarchy?
Now that we know what exception and cancellation are, let’s see how an exception is raised in a coroutine and propagates in the hierarchy.
Scenario 1: Coroutines provide cooperative cancellation, i.e., if a child has failed to complete its job or cancelled, it passes its exception to its parent coroutine.

The diagram shows that the runBlocking
coroutine has three child coroutines. So, if the first gets cancelled, then it will pass the CancellationException
to its parent, i.e., runBlocking
, and the parent will simply ignore the exception because it is an excepted behavior for a cancelled job.
The code is the implementation of Image 1. We can see that launch 1 is not printed in the logs because it was cancelled but the rest of the jobs were executed.
Scenario 2: If a child of a coroutine failed to complete its job and throws an uncaught exception. Again the exception is passed on to the parent, but in this case, the parent will cancel all its child’s jobs and itself.

We see that once launch 1 throws an exception, the third launch failed to complete because the parent cancelled all of its child’s jobs.
Well! The above two scenarios depict the cooperative cancellation and show how the exception is passed to the root of the hierarchy. Now that we know about exception propagation, let’s move further.
How to Handle Exceptions in Coroutines?
It is possible to modify the uncaught exception behavior by using CoroutineExceptionHandler
. This is an exception handler that can be used with the root parent and act a as generic catch block. Once we’re in the exception handler block, we can’t do much about the exception because the coroutine has already completed its execution when the handler is called.
CoroutineExceptionHandler is called only on uncaught exceptions which are not handled otherwise.
Scenario 1: Let’s first see what if we pass this handler to our child coroutine.
As we can see, we create a CoroutineExceptionHandler
object and pass it our launch 1. If we run this code, we’ll see that in the output launch 3 is not called and no exception log is printed. Why?
Well, that’s because launch 1 is a child of runBlocking
, and it’ll pass its exception to its parent so the exception handler in such cases are ineffective.
Scenario 2: Now let’s see what if we pass this handler to a root coroutine.
Here, launch 1 is a root coroutine which means it is not a child of runBlocking
. It acts as a parent to its children if any. Now when the root coroutine throws an exception, we get the exception log printed which shows that passing the exception handler works only with the root coroutine instead of child.
Scenario 3: Let’s also look at Scenario 2 with the async builder.
Run the code and we see that now the exception is caught in the catch block even after passing the handler in the root async builder. Now, why is that?
Well, the reason for this is the async builder returns its result wrapped in deferred object which means that even if there is an uncaught exception, it’ll be wrapped in the deferred.
So calling await()
on the deferred means we’re waiting for the result via the await call and hence this await call receives the exception and throws it. Because it is inside a catch block so we get the exception caught in the catch block and not in the handler.
We might have a doubt here that why are we not passing the handler to the runBlocking? The answer goes as follows:
“CoroutineExceptionHandler is always installed to a coroutine that is created in GlobalScope. It does not make sense to install an exception handler to a coroutine that is launched in the scope of the main runBlocking, since the main coroutine is going to be always cancelled when its child completes with exception despite the installed handler.”
What Is Exception Aggregation?
When multiple children fail with an exception, the very first wins the propagation race and the rest are attached as suppressed ones. This is called Exception aggregation.
This feature is supported only JDK 7+ and the output looks like this
CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]
where the IOException is thrown by one child and Arithmetic by another.
Whoa! Finally, we got a clear understanding of how can we handle the exceptions in coroutines. Do try to run the code snippets by yourself and play around with different scenarios to understand the functionality.
In case you want to read about coroutine basics, you can read here and here.
Hope this would be helpful to you in some way. Will try to bring more content in a simplified manner. If you have any suggested topics to be covered, then let me know.
Let’s connect on Medium or on GitHub if you like the content. Subscribe to emails to get immediate update on new topics.
That is all for now. Stay tuned!
Until next time.
Cheers!