How is a Kotlin suspend function represented in bytecode, and how does kotlin-reflect expose it that java.lang.reflect cannot?
answer
- CPS transform adds Continuation param
- Return erased to Object + COROUTINE_SUSPENDED
- isSuspend = true
- Continuation hidden from parameters
- Reflective call needs a Continuation / startCoroutine
basics
~10 sA suspend function compiles to a normal method with an extra hidden Continuation parameter. Java reflection just sees that odd extra parameter; kotlin-reflect tells you it's actually suspend via isSuspend.
solid answer
~40 sThe compiler applies a CPS (continuation-passing-style) transform: `suspend fun f(x: Int): String` becomes a JVM method `f(int, Continuation): Object` that returns the result or the COROUTINE_SUSPENDED marker. Through java.lang.reflect you see an extra trailing `kotlin.coroutines.Continuation` parameter and an erased `Object` return, with no hint it's a coroutine. kotlin-reflect's `KFunction.isSuspend` is true, the synthetic Continuation parameter is hidden from `KFunction.parameters`, and `returnType` reflects the real declared type. You generally do NOT call suspend functions via reflection directly because you'd have to construct a Continuation by hand; you call them from a coroutine or via startCoroutine. So kotlin-reflect's added value here is faithful introspection (isSuspend, clean parameter list, true return type), not a magic invoker.
code
kotlin · 8 linesimport kotlin.reflect.full.functions
class Repo { suspend fun load(id: Int): String = "u$id" }
val fn = Repo::class.functions.first { it.name == "load" }
println(fn.isSuspend) // true
println(fn.parameters.map { it.name }) // instance + id, NO Continuation
println(fn.returnType) // kotlin.String (not Object)go deeper
Knows suspend adds a hidden Continuation parameter behind the scenes.
Knows isSuspend exists and that Java reflection shows the extra Continuation and Object return.
Explains the CPS transform, COROUTINE_SUSPENDED, hidden parameter, and the difficulty of reflective invocation.
Designs framework routing (e.g., coroutine-aware endpoints) keyed on isSuspend and chooses generated invokers over reflective coroutine calls.
## What suspend means `suspend` marks a function that can pause and resume without blocking a thread. The Kotlin compiler implements it with a **CPS (continuation-passing-style)** transformation rather than any JVM-native coroutine support. ## Bytecode shape `suspend fun load(id: Int): User` is lowered to roughly: ``` Object load(int id, Continuation<? super User> $cont) ``` - An extra trailing **`Continuation`** parameter is appended (the resume callback / state machine). - The return type is erased to **`Object`**, because the function may instead return the special **`COROUTINE_SUSPENDED`** marker when it suspends. - The body is rewritten into a state machine that resumes via the continuation. ## The java.lang.reflect view (confusing) `Method.getParameterTypes()` shows `[int, kotlin.coroutines.Continuation]` and `getReturnType()` shows `Object`. Nothing tells you this is a coroutine or what the real return type was — you'd have to infer it from the Continuation's generic argument. ## The kotlin-reflect view (faithful) ```kotlin import kotlin.reflect.full.functions class Repo { suspend fun load(id: Int): String = "u$id" } fun main() { val fn = Repo::class.functions.first { it.name == "load" } println(fn.isSuspend) // true println(fn.parameters.map { it.name }) // [null(instance), id] -- no Continuation println(fn.returnType) // kotlin.String } ``` - `KFunction.isSuspend` exposes the coroutine nature. - The synthetic `Continuation` parameter is **omitted** from `parameters`. - `returnType` is the **declared** type (`String`), not erased `Object`. ## Invoking a suspend function reflectively This is the catch: kotlin-reflect does **not** give you a blocking magic call. `KFunction.call` on a suspend function still expects a `Continuation` (you'd supply one), which is awkward. The idiomatic approaches: - Obtain a callable reference and use the stdlib `startCoroutine` / `createCoroutine` to drive it, or - Call it from within a coroutine where the compiler supplies the continuation, or - Wrap and call via `kotlinx-coroutines` (`runBlocking`/`async`) when you control the call site. Reflective invocation of suspend functions is rarely needed and is a known sharp edge. ## Takeaway kotlin-reflect's advantage for suspend is **introspection accuracy** (isSuspend, clean parameters, true returnType), letting frameworks recognize and route coroutine endpoints — not a simpler invocation path.
- What does Java reflection report as the return type of a suspend function?java.lang.Object, because the lowered method may return either the result or the COROUTINE_SUSPENDED marker, so the type is erased.
- Why is reflectively invoking a suspend function awkward?The compiled method requires a Continuation argument; without a coroutine context you'd have to construct one yourself or use startCoroutine, which most code avoids.
saying these in an interview costs you the question
- Claiming KFunction.call on a suspend function blocks and returns normally with no Continuation
- Saying the JVM has native coroutine support
- Not knowing the extra Continuation parameter appears in Java reflection
- Thinking Java reflection can tell a suspend function from a regular one