kotlin:S6311 incorrectly reported when using CoroutineExceptionHandler

Then tell us:

  • What language is this for?

Kotlin

  • Which rule?

kotlin:S6311

  • Why do you believe it’s a false-positive/false-negative?

The rule is incorrectly triggered when a CoroutineExceptionHandler is provided as part of the CoroutineContext to a coroutine builder like launch, even if no specific Dispatcher is provided alongside it.

In Kotlin coroutines, the context passed to launch (or other builders) can contain multiple elements, including a Job, a Dispatcher, a CoroutineName, and a CoroutineExceptionHandler. Providing only an ExceptionHandler in the context argument to launch does not change the Dispatcher the coroutine runs on; it inherits the dispatcher from the parent scope. The purpose of providing the handler is solely for centralized exception management within that coroutine, not for thread switching.

Therefore, flagging launch(myExceptionHandler) { suspendingFunction() } seems incorrect, as it doesn’t violate the principle the rule aims to enforce (avoiding unnecessary thread switching for non-blocking suspend functions). It’s treating the presence of any context element, specifically the CoroutineExceptionHandler, as if it implies a thread switch, which is not the case.

  • Are you using

SonarQube Cloud

  • How can we reproduce the problem? Give us a self-contained snippet of code (formatted text, no screenshots)
val dummyExceptionHandler = CoroutineExceptionHandler { context: CoroutineContext, throwable: Throwable ->
    println("Caught exception in context $context: ${throwable.message}")
}
val exampleScope = CoroutineScope(Dispatchers.Default + SupervisorJob())

suspend fun callee() {
    delay(10)
    println("Callee finished successfully.")
}

fun caller() {
    // This line is incorrectly flagged by S6311
    exampleScope.launch(dummyExceptionHandler) {
        callee()
    }
}

Hello @JonatanPlesko,

Thanks for reaching out. This is indeed a regression in the rule after migration to Analysis API with K2 due to the difference in handling parameters. I’ve opened a ticket for it and the fix will follow with the next release.

Best,
Margarita