Closed
Description
See discussion here: https://youtrack.jetbrains.com/issue/KT-24481
The following code clearly demonstrates the problem:
import java.util.concurrent.*
fun see(f: CompletableFuture<*>, s: String) {
f.whenComplete { res, err ->
val getRes =
try { f.get().toString() }
catch (e: Throwable) { e.toString() }
println("$s: Completed" +
"\n\twhenComplete result = $res" +
"\n\twhenComplete error = $err" +
"\n\t get() result = $getRes")
}
}
fun main(args: Array<String>) {
class DomainSpecificException : RuntimeException()
val cf1 = CompletableFuture<Int>()
val cf2 = CompletableFuture<Int>()
val cf3 = cf1.thenCompose { cf2 }
see(cf1,"cf1")
see(cf2,"cf2")
see(cf3,"cf3")
cf2.completeExceptionally(DomainSpecificException())
cf1.complete(239)
}
The result is:
cf2: Completed
whenComplete result = null
whenComplete error = KT24481Kt$main$DomainSpecificException
get() result = java.util.concurrent.ExecutionException: KT24481Kt$main$DomainSpecificException
cf1: Completed
whenComplete result = 239
whenComplete error = null
get() result = 239
cf3: Completed
whenComplete result = null
whenComplete error = java.util.concurrent.CompletionException: KT24481Kt$main$DomainSpecificException
get() result = java.util.concurrent.ExecutionException: KT24481Kt$main$DomainSpecificException
You can clearly see that CompletableFuture.get()
not just simply wraps the resulting exception into ExecutionException
, but it also unwraps CompletionException
before doing so. This behavior can be confirmed from its source, too. This CompletionException
unwrapping behavior shall be replicated by CompletionStage.await()
slow path, too.
The goal is that CompletionStage.await()
shall throw unwrapped DomainSpecificException
for both cf2
and cf3
in the above example.