Why I Stopped Using Kotlin Coroutines: Debugging, Context Propagation, and Performance Concerns
The article shares personal experiences with Kotlin coroutines, highlighting debugging challenges, loss of context across threads, pitfalls of using ThreadLocal, synchronization issues, and performance trade‑offs, ultimately explaining why the author stopped using coroutines for server‑side development.
I enjoy Kotlin but have encountered several drawbacks when using Kotlin coroutines together with the Ktor server framework, especially compared with traditional Spring Boot approaches.
Debugging
When stepping over a suspend function like suspend fun retrieveData(): SomeData { ... }, the debugger often skips the call to remoteCall because the coroutine yields and resumes on a different thread, breaking the usual thread‑bound debugging model. The only reliable workaround is to set breakpoints on the exact lines you want to inspect, which is cumbersome.
suspend fun retrieveData(): SomeData {
val request = createRequest()
val response = remoteCall(request)
return postProcess(response)
}
private suspend fun remoteCall(request: Request): Response {
// do suspending REST call
}Additionally, determining what a particular coroutine is doing at any moment is difficult because it can hop between threads. While coroutine names can be logged, the mental overhead remains higher than with plain thread‑based code.
REST Calls and Context Propagation
In micro‑service architectures, it is common to propagate authentication data from an incoming REST request to downstream calls. With thread‑based frameworks like Spring, ThreadLocal (e.g., SecurityContextHolder) is used to store such context. Coroutines break this model because a coroutine may resume on a different thread, rendering ThreadLocal ineffective.
Kotlin provides CoroutineContext, essentially a map that travels with the coroutine, but it does not automatically inherit parent context. Consequently, developers must manually pass context elements to builders like async, launch, or runBlocking, or risk losing the data.
suspend fun sum(): Int {
val jobs = mutableListOf<Deferred<Int>>()
for (child in children) {
jobs += async { // we lose our context here!
child.evaluate()
}
}
return jobs.awaitAll().sum()
}Each new coroutine starts with an empty context unless explicitly supplied, and there is no automatic fallback to a parent’s context, so developers must handle propagation themselves.
synchronized Does Not Behave as Expected
Using traditional Java synchronization primitives (e.g., synchronized blocks or ReentrantLock) with suspend functions can lead to dangerous situations: a coroutine may yield while still holding the lock, allowing another thread to resume the coroutine without owning the lock, potentially causing deadlocks or data races.
val lock = ReentrantLock()
suspend fun doWithLock() {
lock.withLock {
callSuspendingFunction()
}
}Because many Java libraries assume a thread‑bound execution model, introducing coroutines can render them unsafe or unusable.
Throughput vs. Horizontal Scaling
Coroutines can increase per‑thread request throughput for I/O‑bound workloads, but they also introduce non‑zero overhead. In cloud or Kubernetes environments, horizontal scaling (adding more instances) often outweighs the benefits of higher single‑instance throughput, reducing the overall value of coroutines for server‑side code.
When Coroutines Are Valuable
Coroutines shine in single‑UI‑thread client applications (e.g., Android) where they simplify code structure while respecting UI thread constraints. For server‑side development, however, the author finds them less compelling, noting ongoing JVM efforts like Fibers that may eventually provide better integration.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
High Availability Architecture
Official account for High Availability Architecture.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
