skip to content

Explain how Kotlin `vararg` maps to JVM varargs for interop, how spread interacts with calling Java varargs methods, and one subtlety of overload resolution between a vararg overload and a fixed-arity overload.

level: principalimportance: nice to knowfreq 20%

answer

  1. vararg -> JVM T[] with ACC_VARARGS flag
  2. Java may pass array directly; Kotlin needs *
  3. Fixed-arity overload beats vararg (more specific)
  4. Generic vararg erases to Object[]
  5. Reified needed for real runtime element type

basics

~20 s

Kotlin vararg compiles to a normal JVM varargs array, so Java code can call it and Kotlin can call Java varargs the same way. To pass an existing array to either, use the * spread. When both a vararg and an exact fixed overload match, Kotlin prefers the more specific fixed-arity one.

solid answer

~40 s

A Kotlin `vararg t: T` compiles to a JVM method taking `T[]` (an array) with the ACC_VARARGS flag, so it is callable from Java as `m(a, b, c)` and from Kotlin as usual. Calling a **Java** varargs method from Kotlin works symmetrically: pass comma-separated args, or spread an existing array with `*arr`. Java permits passing an array directly to a varargs slot without any spread; **Kotlin requires the explicit `*`** even for Java methods. For overload resolution, Kotlin's rules prefer the **most specific applicable** signature: a fixed-arity overload that matches the exact argument count and types is chosen over a `vararg` overload, because varargs candidates are considered less specific. This mirrors Java. Generic `vararg t: T` where `T` is a type parameter compiles to `Object[]` after erasure, which affects reified/array-of-generic situations.

code

kotlin · 10 lines
kotlin
fun f(a: Int) = "fixed"
fun f(vararg a: Int) = "vararg"

fun main() {
    println(f(1))      // fixed  (most specific)
    println(f(1, 2))   // vararg
    println(f())       // vararg (fixed needs 1 arg)
    val arr = intArrayOf(3, 4)
    println(f(*arr))   // vararg via spread
}

go deeper

for a junior

May know Kotlin vararg maps to an array but not interop/overload details.

for a middle

Knows Java interop uses arrays and that Kotlin needs * to spread into Java varargs.

for a senior

Explains ACC_VARARGS mapping, fixed-vs-vararg specificity, and the Kotlin-vs-Java spread asymmetry.

for a principal

Adds erasure-to-Object[] and reification, allocation/ABI trade-offs, and API guidance (vararg vs Collection).

## JVM mapping Kotlin `fun m(vararg t: T)` compiles to a JVM method whose last parameter is an array `T[]` and which carries the **`ACC_VARARGS`** flag. Concretely: - `vararg s: String` → `m(String[])` - `vararg x: Int` → `m(int[])` (primitive array, no boxing) - `vararg t: T` with type parameter `T` → erases to `m(Object[])` Because of the varargs flag, **Java callers** can write `m("a", "b")` naturally. ## Calling Java varargs from Kotlin Kotlin can call any Java varargs method. Two ways to pass an existing array: ```kotlin val parts = arrayOf("a", "b") String.format("%s %s", *parts) // spread required in Kotlin ``` Key asymmetry: **Java** lets you pass an array directly into a varargs slot (`format(fmt, arr)`), but **Kotlin mandates the explicit `*` spread**. Forgetting it gives a type error (you would be passing one `Array<String>` where the element type is expected). For `Array<out Any?>`/`Object...` cases, watch for the classic nested-array ambiguity — spreading an `Object[]` whose single element is itself an array. ## Overload resolution: fixed vs vararg Kotlin chooses the **most specific** applicable candidate: ```kotlin fun f(a: Int) = "fixed" fun f(vararg a: Int) = "vararg" f(1) // "fixed" — exact fixed-arity wins f(1, 2) // "vararg" — only the vararg form applies f() // "vararg" — fixed needs one arg ``` The fixed-arity overload is preferred for `f(1)` because a vararg candidate is treated as **less specific**. This matches Java's phase ordering (fixed-arity applicability is considered before variable-arity). It means adding a vararg overload will not silently steal calls that an exact overload already satisfies. ## `@JvmStatic`/named-arg subtleties - If a `vararg` is not last, callers must use **named arguments** for trailing params; but the spread/vararg JVM signature still puts the array in declaration order. - Spreading an empty array (`*emptyArray()`) is valid and yields zero arguments. ## Generics and erasure Because `vararg t: T` erases to `Object[]`, you cannot rely on the runtime element type; combine with `reified` type parameters in `inline` functions if you need the actual `Class<T>` for array creation (`arrayOfNulls<T>`-style needs reification). ## Practical guidance - Prefer accepting a `List`/`Collection` for large or already-collection data to avoid spread copies; reserve `vararg` for ergonomic small fixed-prefix call sites. - Remember Kotlin's mandatory `*` when migrating Java code that passed arrays to varargs directly.

  • Why must Kotlin use `*` to pass an array to a Java varargs method when Java doesn't?
    Kotlin's type system treats the array as a single value; the explicit spread operator is the only way to ask the compiler to expand it into individual varargs arguments, removing the array-vs-element ambiguity.
  • What does `vararg t: T` (type parameter T) erase to on the JVM?
    `Object[]` — the element type is erased, so runtime element-type info requires a reified type parameter on an inline function.

saying these in an interview costs you the question

  • Saying Kotlin can pass an array to varargs without `*` like Java
  • Claiming the vararg overload is chosen over an exact fixed overload
  • Thinking primitive varargs box into Object[]
  • Asserting generic vararg keeps its runtime element type without reification
  • Believing Java cannot call Kotlin vararg methods naturally

context