skip to content

How is a Kotlin suspend function represented in bytecode, and how does kotlin-reflect expose it that java.lang.reflect cannot?

level: seniorimportance: should knowfreq 30%

answer

  1. CPS transform adds Continuation param
  2. Return erased to Object + COROUTINE_SUSPENDED
  3. isSuspend = true
  4. Continuation hidden from parameters
  5. Reflective call needs a Continuation / startCoroutine

basics

~10 s

A 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 s

The 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 lines
kotlin
import 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

for a junior

Knows suspend adds a hidden Continuation parameter behind the scenes.

for a middle

Knows isSuspend exists and that Java reflection shows the extra Continuation and Object return.

for a senior

Explains the CPS transform, COROUTINE_SUSPENDED, hidden parameter, and the difficulty of reflective invocation.

for a principal

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

context