Kotlin coroutine can't handle exception

The behavior of your second example is correct, this is the work of structured concurrency. Because the inner async block throws an exception, this coroutine is cancelled. Due to structured concurrency the parent job is cancelled as well.

Look at this small example:

val result = coroutineScope {
    async {
        throw IllegalStateException()
    }
    10
}

This block will never return a value, even if we never request the async result. The inner coroutine is cancelled and the outer scope is cancelled as well.

If you don't like this behavior you can use the supervisorScope. In this case the inner coroutine can fail without failing the outer coroutine.

val result = supervisorScope {
    async {
        throw IllegalStateException()
    }
    10
}

In your first example you catch the exception inside of the coroutine block, because of this, the coroutine exits normally.

For discussion of this topic see:

  • https://github.com/Kotlin/kotlinx.coroutines/issues/552
  • https://github.com/Kotlin/kotlinx.coroutines/issues/763

I got struck by this behavior just yesterday, here's my analysis.

In a nutshell, this behavior is desired because async does not have the same purpose as in other languages. In Kotlin you should use it sparingly, only when you have to decompose a task into several subtasks that run in parallel.

Whenever you just want to write

val result = async { work() }.await()

you should instead write

val result = withContext(Default) { work() }

and this will behave the expected way. Also, whenever you have the opportunity, you should move the withContext call into the work() function and make it a suspend fun.