You write a Kotlin function fun greet(name: String, greeting: String = "Hello"). A Java colleague says they can only call it by passing both arguments. Why can't Java omit the defaulted parameter, and what is the simplest fix?
answer
- Defaults live in @Metadata, not in the JVM signature
- Synthetic name$default bridge with int mask
- Java only sees the full-arity method
- @JvmOverloads generates telescoping overloads
- Overloads forward to the full method
basics
~10 sKotlin default values live only in Kotlin metadata, not in the compiled method signature Java sees. So Java must pass every argument. Adding @JvmOverloads makes Kotlin also generate the shorter overloads Java can call.
solid answer
~40 sDefault parameter values are a Kotlin-language feature, not a JVM feature. The compiler emits one real method greet(String, String); the default "Hello" is recorded in @Metadata and applied at Kotlin call sites, plus there is a hidden synthetic greet$default(...) bridge. Java sees neither the metadata nor the synthetic bridge as a usable overload, so it must supply both arguments. The fix is to annotate the function with @JvmOverloads, which tells the compiler to additionally generate the telescoping overloads greet(String) and greet(String, String). Each generated overload simply forwards to the full method with the defaults filled in, so Java callers get the same convenience Kotlin callers have. Without @JvmOverloads you would otherwise write the overloads by hand.
code
kotlin · 5 lines@JvmOverloads
fun greet(name: String, greeting: String = "Hello"): String = "$greeting, $name"
// Without @JvmOverloads, Java must write: greet("Sam", "Hello")
// With @JvmOverloads, Java may write: greet("Sam")go deeper
Knows defaults are Kotlin-only and that @JvmOverloads is the fix for Java callers.
Can explain the metadata vs. signature split and that one real method plus a synthetic helper is generated.
Articulates the right-to-left overload generation and that overloads forward to the full method, plus reflection implications.
Frames it as an API-design decision: which library functions need @JvmOverloads for stable Java/Kotlin parity and binary compatibility.
## The core idea Kotlin lets you give a parameter a **default value** (`greeting: String = "Hello"`). On the JVM there is **no such thing** as a default parameter — a Java method signature is just its name plus its full parameter list. So Kotlin has to bridge the gap. ## What the compiler actually emits For `fun greet(name: String, greeting: String = "Hello")` the Kotlin compiler produces: 1. **One real method**: `greet(String, String)` — the full signature, no defaults baked into the bytecode signature. 2. **A synthetic helper** named `greet$default(String, String, int, Object)`. The extra `int` is a **bitmask** telling the helper which arguments were omitted; the trailing `Object` is a `DefaultConstructorMarker`-style placeholder. When Kotlin code calls `greet("Sam")`, it actually invokes `greet$default` with the mask bit set, and the helper substitutes the default. 3. The default value itself is recorded in the `@Metadata` annotation so the Kotlin compiler (not the JVM) knows about it at every Kotlin call site. ```kotlin fun greet(name: String, greeting: String = "Hello") = "$greeting, $name" // Kotlin: legal, compiler routes through greet$default greet("Sam") ``` ## Why Java is stuck Java ignores `@Metadata`, and the `greet$default` helper is marked **synthetic** (and takes weird extra parameters), so it is not a clean overload Java should call. Java only sees `greet(String, String)`. Therefore Java **must pass both arguments**. ## The fix: @JvmOverloads Annotating the function makes the compiler **also** emit ordinary overloads for each trailing default, from right to left: ```kotlin @JvmOverloads fun greet(name: String, greeting: String = "Hello") = "$greeting, $name" // Generated: greet(String) AND greet(String, String) ``` Now Java can call `greet("Sam")` directly. Each generated overload forwards to the full method with the default filled in. This is the standard, idiomatic fix; the only alternative is writing the overloads by hand. ## Key terms - **Synthetic**: compiler-generated, hidden from normal source-level access. - **Bitmask (`int`)**: each bit marks one omitted argument so the `$default` helper knows what to substitute. - **`@JvmOverloads`**: opt-in annotation that generates Java-friendly telescoping overloads.
- Does @JvmOverloads change anything for Kotlin callers?No. Kotlin callers already use the default mechanism; @JvmOverloads only adds extra JVM overloads for Java/reflection. Kotlin behavior is unchanged.
- If only the second of two parameters has a default, what overloads does @JvmOverloads generate?It generates overloads for each trailing default removed right-to-left: here just greet(String) plus the original greet(String, String).
Default values are like a note pinned to the recipe (metadata) saying 'salt optional' — only Kotlin reads the note; Java just sees the full ingredient list.
saying these in an interview costs you the question
- Claiming the JVM supports default parameters natively
- Saying Java can call the function with one argument without @JvmOverloads
- Confusing @JvmOverloads with @JvmStatic or @JvmName
- Believing @JvmOverloads changes Kotlin call-site behavior
- Thinking you must duplicate the body in each overload