skip to content

Default-Argument Bridge Methods

Default arguments are implemented by a synthetic bridge method taking a bitmask of which parameters were supplied. Java never sees it usefully, which is exactly why @JvmOverloads exists.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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?

level: juniorimportance: must knowfreq 70%

answer

  1. Defaults live in @Metadata, not in the JVM signature
  2. Synthetic name$default bridge with int mask
  3. Java only sees the full-arity method
  4. @JvmOverloads generates telescoping overloads
  5. Overloads forward to the full method

basics

~10 s

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

Default 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
kotlin
@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

for a junior

Knows defaults are Kotlin-only and that @JvmOverloads is the fix for Java callers.

for a middle

Can explain the metadata vs. signature split and that one real method plus a synthetic helper is generated.

for a senior

Articulates the right-to-left overload generation and that overloads forward to the full method, plus reflection implications.

for a principal

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

context

open as a page

Describe the exact bytecode the Kotlin compiler emits for a top-level function with default parameters. Name the synthetic method, its extra parameters, and what each does.

level: middleimportance: should knowfreq 55%

basics

~20 s

Besides the normal method, Kotlin emits a synthetic helper named function$default. It takes the original parameters plus an int bitmask (which args were omitted) and a trailing Object marker. It fills in defaults, then calls the real method.

open as a page

How do default parameters work for constructors specifically, and what is DefaultConstructorMarker? Why does Kotlin use a dedicated marker type for constructors rather than a plain int-only bridge?

level: seniorimportance: should knowfreq 40%

basics

~20 s

For a constructor with defaults, Kotlin generates an extra synthetic constructor that takes the params, an int bitmask, and a DefaultConstructorMarker argument. The marker gives this synthetic constructor a unique signature so it can't clash with a real one.

open as a page

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%

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.

open as a page

You maintain a Kotlin library consumed by Java. Walk through the tradeoffs of @JvmOverloads versus manual overloads, and explain the binary-compatibility risks of adding or reordering default parameters on a published API.

level: principalimportance: nice to knowfreq 30%

basics

~10 s

@JvmOverloads auto-generates Java-friendly overloads with no boilerplate but you control them less. Adding or reordering default parameters changes the synthetic $default signature and call-site bitmasks, so already-compiled callers can break.

open as a page