Show how two Kotlin functions can produce a JVM signature clash due to type erasure, and how @JvmName resolves it.
answer
- Generics erase: List<String> and List<Int> -> List
- Same descriptor = 'platform declaration clash'
- @JvmName splits the bytecode names
- Kotlin still sees overloads; Java needs both names
- Common with extension-receiver overloads
basics
~20 sGenerics disappear at runtime, so List<String> and List<Int> both become plain List on the JVM. Two functions that differ only by that generic part get the same JVM signature and won't compile. Adding @JvmName to one gives it a different bytecode name, so both compile.
solid answer
~40 sOn the JVM, generic type arguments are erased: `List<String>` and `List<Int>` both erase to `List`. If two Kotlin functions share a name and differ only in an erased generic parameter (receiver or argument), they compile to identical JVM descriptors (e.g. both `process(Ljava/util/List;)V`), which the JVM forbids — Kotlin reports a 'platform declaration clash' / 'accidental override' error. `@JvmName` on at least one of them emits a distinct method name in bytecode, eliminating the descriptor collision. Kotlin source still sees overloaded names because Kotlin overload resolution uses the full (non-erased) generic signatures; only the bytecode names diverge. This is the canonical reason @JvmName exists for member and extension functions whose Kotlin overloads are erasure-equivalent.
code
kotlin · 8 linesfun List<String>.flatten2(): String = joinToString()
@JvmName("flattenInts")
fun List<Int>.flatten2(): Int = sum()
// Without @JvmName: error "Platform declaration clash: ... have the same JVM signature (flatten2(Ljava/util/List;))"
// Kotlin call sites: listOfStrings.flatten2(); listOfInts.flatten2()
// Java call sites: flatten2(stringList); flattenInts(intList);go deeper
Recognizes that generics erase and that @JvmName can fix a name conflict, even if hazy on the descriptor detail.
Constructs a concrete erasure-clash example and applies @JvmName correctly, naming the 'platform declaration clash' error.
Explains descriptors, why Kotlin overload resolution still works, and when to prefer different Kotlin names over @JvmName.
Weighs API ergonomics across Kotlin and Java consumers and the long-term cost of dual names; reasons about reified vs. erased generics in API design.
## The problem: type erasure **Type erasure** means the JVM does not keep generic type arguments at runtime. A function taking `List<String>` and another taking `List<Int>` both have the **erased** parameter type `java.util.List`. The JVM identifies a method by its **descriptor**: name + erased parameter types + erased return type. If two methods produce the same descriptor in the same class, the class file is invalid. ## Where Kotlin lets you write the clash Kotlin's overload resolution operates on full generic signatures, so the source below looks fine to a Kotlin reader, but it cannot be emitted: ```kotlin // COMPILE ERROR: "Platform declaration clash" fun process(items: List<String>) { /* ... */ } fun process(items: List<Int>) { /* ... */ } // Both erase to: process(Ljava/util/List;)V ``` The same happens for **extension functions** that differ only by an erased generic receiver: ```kotlin fun List<String>.sum2(): String = joinToString() fun List<Int>.sum2(): Int = sum() // clash: both are sum2(List) ``` ## The fix: @JvmName Annotate one (or both) of the clashing functions to give it a different **bytecode** name: ```kotlin fun process(items: List<String>) { /* ... */ } @JvmName("processInts") fun process(items: List<Int>) { /* ... */ } ``` Now the descriptors are `process(Ljava/util/List;)V` and `processInts(Ljava/util/List;)V` — distinct, valid. **Kotlin callers** keep calling `process(...)` for both (overload resolution picks by full type). **Java callers** must use `process` for the String version and `processInts` for the Int version. ## Important nuances - This only works because the clash is a **naming** collision, not a semantic one — @JvmName separates the bytecode names. - For **inline functions with reified type parameters**, you often don't hit the clash because reified generics are resolved at the call site, but for ordinary (non-reified) generics the erasure clash is real. - An alternative to @JvmName is simply giving the functions different Kotlin names; @JvmName is preferred when you want a uniform, idiomatic Kotlin name (`process`) while still emitting valid bytecode. ## Summary Erasure makes generic-only-different overloads collide on the JVM; `@JvmName` resolves it by decoupling the Kotlin name from the emitted bytecode name.
- Why does Kotlin still let you call both overloads by the same name from Kotlin?Kotlin overload resolution uses the full, non-erased generic signatures, so it can distinguish List<String> from List<Int>; only the emitted JVM names differ.
- What exact error does the compiler give without @JvmName?A 'Platform declaration clash' (a.k.a. 'accidental override') error stating the declarations have the same JVM signature.
saying these in an interview costs you the question
- Saying generics are retained at runtime on the JVM
- Claiming Kotlin can't even compile the @JvmName-fixed version
- Thinking @JvmName changes the parameter types or descriptor beyond the name
- Confusing the fix with @JvmStatic
- Believing reified generics never relate to erasure