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?
answer
- Two methods: real + synthetic $default
- java reflection won't apply defaults
- Synthetic flagged ACC_SYNTHETIC; filter isSynthetic()
- kotlin-reflect callBy honors defaults
- @JvmOverloads or pass all args in tests
basics
~20 sReflection 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 sTools 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
Knows defaults aren't applied automatically when calling reflectively; pass all args.
Explains the two-method generation and that synthetic bridges confuse reflection/mocks; knows @JvmOverloads helps.
Recommends KFunction.callBy / synthetic filtering and reasons about which path a mock must stub.
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