skip to content

A team reports that calling a Kotlin function with default parameters from Java reflection, or matching it in a Mockito mock, behaves unexpectedly. Why do default parameters cause surprises in reflection/mocking, and what should you check?

level: middleimportance: nice to knowfreq 35%

answer

  1. Two methods: real + synthetic $default
  2. java reflection won't apply defaults
  3. Synthetic flagged ACC_SYNTHETIC; filter isSynthetic()
  4. kotlin-reflect callBy honors defaults
  5. @JvmOverloads or pass all args in tests

basics

~20 s

Reflection and mocks see the real method plus an extra synthetic $default method with strange parameters (int mask, marker). They don't apply defaults automatically, so you may pick the wrong method or fail to match. Use @JvmOverloads or call the full method.

solid answer

~40 s

Tools that enumerate methods via java.lang.reflect see both the real method f(...) and the synthetic f$default(..., int, Object) bridge. Reflection does not understand Kotlin defaults, so Method.invoke won't fill them in — you must either invoke the full method with all arguments or invoke the synthetic bridge with a correctly computed bitmask (fragile, not recommended). Mockito/byte-buddy may stub or intercept the synthetic bridge separately from the real method, so a when(...) set on one path won't trigger when calls route through the other. The synthetic method is flagged ACC_SYNTHETIC, so well-behaved frameworks should skip it, but not all do. The robust fixes: add @JvmOverloads to expose clean overloads, or prefer kotlin-reflect (KFunction.callBy) which understands defaults, or simply pass every argument explicitly in tests.

go deeper

for a junior

Knows defaults aren't applied automatically when calling reflectively; pass all args.

for a middle

Explains the two-method generation and that synthetic bridges confuse reflection/mocks; knows @JvmOverloads helps.

for a senior

Recommends KFunction.callBy / synthetic filtering and reasons about which path a mock must stub.

for a principal

Sets team conventions for testing/reflection over Kotlin APIs and weighs @JvmOverloads on public surfaces for tool-friendliness.

## Why defaults surprise reflection and mocks Default parameters generate **two JVM methods**: the real `f(...)` and a **synthetic** `f$default(..., int mask, Object marker)`. Reflection and bytecode-based mocking libraries enumerate methods directly, so they encounter **both**, and neither understands Kotlin's default semantics. ### Java reflection `java.lang.reflect.Method.invoke` calls a method by its **exact** signature. It will **not** apply Kotlin defaults — there is no place for it to. So: - If you grab `f(...)` you must supply **every** argument. - The `f$default` bridge can technically be invoked, but you'd have to compute the **bitmask** and pass the **marker** (`null`) yourself — brittle and not intended. - The bridge is flagged **`ACC_SYNTHETIC`**; reflective scans that don't filter `Method.isSynthetic()` may accidentally pick it. ### Mockito / byte-buddy Mocking frameworks subclass/intercept methods. Because the real method and the `$default` bridge are **separate methods**, a stub configured on one may **not** fire when the call routes through the other. A Kotlin caller omitting a default routes through `$default`; a Java caller (or explicit full call) hits the real method. Mismatched matchers fail silently. ```kotlin fun fetch(id: Long, useCache: Boolean = true): Data = ... // Java reflection: must call fetch(id, true) OR deal with fetch$default + mask // Mockito: stub the path your code actually invokes ``` ## What to check / fixes - **Add `@JvmOverloads`** so clean overloads exist and tools target the natural signature. - **Use kotlin-reflect**: `KFunction.callBy(mapOf(...))` understands defaults and fills omitted parameters correctly — the *only* reflection path that honors Kotlin defaults. - **In tests, pass all arguments explicitly** to hit the real method deterministically. - **Filter synthetics**: skip `Method.isSynthetic()` methods in reflective scans. ## Key terms - **`ACC_SYNTHETIC`**: bytecode flag marking compiler-generated members; `Method.isSynthetic()` exposes it. - **`KFunction.callBy`**: kotlin-reflect call that accepts a parameter map and applies defaults. - **Bridge routing**: omitted-default Kotlin calls go through `$default`, full calls go direct.

  • Which reflection API correctly applies Kotlin default values?
    kotlin-reflect's KFunction.callBy(args) — it accepts a map of supplied parameters and fills in the rest from defaults. Plain java.lang.reflect cannot.
  • How do you avoid accidentally picking the synthetic bridge when scanning methods?
    Skip methods where Method.isSynthetic() is true (or whose names contain the $default suffix).

saying these in an interview costs you the question

  • Claiming java.lang.reflect applies Kotlin defaults
  • Not knowing the synthetic bridge appears in method scans
  • Manually invoking $default with a guessed bitmask in production
  • Unaware of KFunction.callBy
  • Assuming a Mockito stub covers both real and bridge paths automatically

context