⚠️"uncaught exception" vocabulary does NOT include all unhandled exceptions⚠️
When you read official documentation of CoroutineExceptionHandler
You will see usage of "uncaught exception" vocabulary.
CoroutineExceptionHandler: An optional element in the coroutine context to handle uncaught exceptions. - kdoc. (hopefully kotlin folk create a page for uncaught exceptions per request)
And you might think, ok got it, uncaught exception is any unhandled exception from a co-routine. You might think if nobody handles the exception and when it escapes from co-routine that it qualifies as uncaught. But NO "uncaught exception" in kotlin land has special meaning, and only some of the unhandled exceptions qualify per their "uncaught exception" definition. See note below...
An exception that can not be propagated to the root via structured concurrency and also can't be returned through some normal communication between coroutines. - dkhalanskyjb/github-convo
To ground the definition let's bring up examples of what can qualify as "uncaught exception" and what can NEVER qualify as uncaught exception.
Examples of "uncaught exception"
Typically means:
- unhandled exception from root launch
- unhandled exception from launch with SupervisorJob
Less typically means:
KDoc references
Normally, uncaught exceptions can only result from root coroutines created using the launch builder.
All children coroutines (coroutines created in the context of another Job) delegate handling of their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root, so the CoroutineExceptionHandler installed in their context is never used.
Coroutines running with SupervisorJob do not propagate exceptions to their parent and are treated like root coroutines.
A coroutine that was created using async always catches all its exceptions and represents them in the resulting Deferred object, so it cannot result in uncaught exceptions. - CoroutineExceptionHandler | kotlinx.coroutines – Kotlin Programming Language
Coroutine builders come in two flavors: propagating exceptions automatically (launch) or exposing them to users (async and produce).
When these builders are used to create a root coroutine, that is not a child of another coroutine, the former builders treat exceptions as uncaught exceptions, similar to Java's Thread.uncaughtExceptionHandler,
while the latter are relying on the user to consume the final exception, for example via await or receive (produce and receive are covered in Channels section). - Coroutine exceptions handling | Kotlin Documentation
Clarify: async CANNOT have "uncaught exception"
A coroutine that was created using async always catches all its exceptions and represents them in the resulting Deferred object, so it cannot result in uncaught exceptions. - kdoc
Since 1) async unhandled exceptions do not qualify as "uncaught exception" and 2) CoroutineExceptionHandler only works on "uncaught exceptions" we can end up with the following gotcha:
⚠️ IF async throws unhandled exception before await THEN exception is lost⚠️
Backlinks