CoroutineContext
In the CoroutineDispatcher section, we learned about Dispatcher and CoroutineExceptionHandler.
- Dispatcher: A manager that holds the thread pool where coroutines will execute.
- CoroutineExceptionHandler: A handler for when an Exception occurs in a coroutine.
Interestingly, these two elements can be placed directly where a CoroutineContext is expected.


This is possible because both Dispatcher and CoroutineExceptionHandler are implementations of interfaces that extend CoroutineContext.
CoroutineContext #
You can think of CoroutineContext as the environment in which a Coroutine executes. The Dispatcher and CoroutineExceptionHandler mentioned above are also part of the coroutine's execution environment, and both can be included in a CoroutineContext to set up the coroutine's execution environment.
Combining CoroutineContexts #
Let's combine a Dispatcher and a CoroutineExceptionHandler to create a single Context.
Here, we use the operator fun plus on CoroutineContext.
public interface CoroutineContext {
public operator fun plus(context: CoroutineContext): CoroutineContext
..
}
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> }
val coroutineContext = Dispatchers.IO + exceptionHandler
Interpreting this code, a single CoroutineContext now includes Dispatcher.IO and CoroutineExceptionHandler, allowing the context to handle exceptions occurring on the IO Thread.

The CoroutineContext created this way can be used by placing it where a CoroutineContext is expected.

Accessing CoroutineContext #
We learned above that CoroutineContext is a collection of CoroutineContexts. Now, we will explore how to access a specific CoroutineContext from such a collection.
The image above was a simplified summary for easy understanding, but expressed in more detail, it looks like this:

There are two CoroutineContexts that make up the CoroutineContext: Dispatcher and CoroutineExceptionHandler. Let's say the key for Dispatcher is "keyA" and for CoroutineExceptionHandler is "keyB". Of course, the key is not actually a string, but we express it this way for easier understanding.
In the image above, we want to retrieve the ExceptionHandler from the parent CoroutineContext.
fun main() {
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> }
val coroutineContext = Dispatchers.IO + exceptionHandler // Parent CoroutineContext = Dispatcher + ExceptionHandler
val exceptionHandlerFromContext = coroutineContext[exceptionHandler.key] // Accessing child CoroutineContext via Key
if (exceptionHandler === exceptionHandlerFromContext) { // Comparing for identity to check if they are the same object
println(true)
}
}
As shown above, it's possible to request a child CoroutineContext by passing a key to the CoroutineContext.
If you compare the value retrieved from the context via the key with the previously combined CoroutineContext using identity (===) comparison, you can see that true is printed.

Removing CoroutineContext from CoroutineContext #
If CoroutineContext can be accessed as shown above, it can naturally also be removed. The removal of a CoroutineContext is possible through the minusKey() method.
fun main() {
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> }
val coroutineContext = Dispatchers.IO + exceptionHandler
val minusContext = coroutineContext.minusKey(exceptionHandler.key)
}
This can be illustrated as follows:
- Request to remove CoroutineExceptionHandler (CoroutineContext)

- Removal complete

- Returns itself
Calling minusKey returns the removed CoroutineContext.
public fun minusKey(key: Key<*>): CoroutineContext
val minusContext = coroutineContext.minusKey(exceptionHandler.key)
