skip to content

JVM Signature Clashes

Erasure can collapse two Kotlin functions onto the same JVM descriptor, producing a platform declaration clash. Renaming one with @JvmName is the standard resolution, and recognizing the error message quickly is the practical skill.

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

questions

5

What is a 'platform declaration clash' in Kotlin, and what commonly causes it?

level: juniorimportance: must knowfreq 55%

answer

  1. Distinct in Kotlin, identical on JVM
  2. Type erasure collapses List<Int>/List<String> to List
  3. Descriptor = name + erased params
  4. @JvmName renames the bytecode method
  5. Kotlin call site unchanged

basics

~10 s

It is a compile error that happens when two Kotlin functions look different in Kotlin but turn into the exact same method on the JVM, so the JVM cannot tell them apart.

solid answer

~40 s

A 'platform declaration clash' is a Kotlin compile-time error raised when two declarations are distinct in Kotlin source but compile to the same JVM method signature (same name + same erased parameter types + return type for some cases). The classic cause is generic type erasure: the JVM erases type arguments, so `fun f(x: List<Int>)` and `fun f(x: List<String>)` both become `f(List)`. Other causes include a property's generated getter colliding with a function, or two functions differing only by a return type the JVM does not consider part of the descriptor. The fix is to make one JVM signature distinct, usually by renaming the bytecode method via the `@JvmName("...")` annotation, which keeps the Kotlin-side names while giving each a unique JVM name.

code

kotlin · 9 lines
kotlin
// Compile error: platform declaration clash
fun sum(xs: List<Int>): Int = xs.sum()
fun sum(xs: List<Long>): Long = xs.sum()

// Fixed
@JvmName("sumInts")
fun sumI(xs: List<Int>): Int = xs.sum()
@JvmName("sumLongs")
fun sumL(xs: List<Long>): Long = xs.sum()

go deeper

for a junior

Can recognize the error message and name type erasure as the cause.

for a middle

Applies @JvmName correctly and explains it only affects the JVM symbol, not Kotlin calls.

for a senior

Explains JVM descriptors precisely and enumerates non-generic triggers (getter/function collisions).

for a principal

Frames API design to avoid the clash entirely and weighs @JvmName vs distinct names for Java consumers.

## What the error means Kotlin compiles to JVM bytecode. On the JVM every method is identified by its **descriptor**: its name plus the (erased) types of its parameters. If two Kotlin declarations end up with the **same JVM descriptor**, the compiler reports a *platform declaration clash* — because the resulting `.class` file would contain two methods the JVM treats as identical, which is illegal. ## Why generics cause it: type erasure The JVM has no generics at runtime. Type arguments like `<Int>` or `<String>` are **erased** — `List<Int>` and `List<String>` both become the raw type `List` (technically `java.util.List`). So these two Kotlin functions: ```kotlin fun process(items: List<Int>) { } fun process(items: List<String>) { } ``` are perfectly valid and distinct in Kotlin, but both compile to the JVM method `process(java.util.List)`. Same descriptor -> clash. ## Other common triggers - A property generating a getter that collides with a same-named function (e.g. `val x` produces `getX()` colliding with `fun getX()`). - Functions differing only in return type after erasure. - An `Int`/`Long` etc. that boxes the same way is *not* a cause — primitives keep distinct descriptors. ## The fix: @JvmName The `@JvmName("...")` annotation renames the **generated bytecode method** without changing how you call it from Kotlin: ```kotlin @JvmName("processInts") fun process(items: List<Int>) { } @JvmName("processStrings") fun process(items: List<String>) { } ``` Now the JVM sees `processInts(List)` and `processStrings(List)` — distinct descriptors, no clash. From Kotlin you still call `process(...)`; from Java you call `processInts(...)` / `processStrings(...)`. ## Key terms - **Descriptor**: the JVM's identity for a method (name + param types). - **Erasure**: dropping generic type arguments at compile time. - **@JvmName**: renames the emitted JVM symbol for a function/getter/setter/file class.

  • Does adding @JvmName change how Kotlin code calls the function?
    No. @JvmName only renames the emitted JVM method; Kotlin call sites resolve by the Kotlin name and are unaffected. Only Java callers see the new name.

Two contacts with different nicknames but the same phone number — your address book (Kotlin) shows two people, but the phone (JVM) can only dial one.

saying these in an interview costs you the question

  • Claiming the JVM keeps generic type arguments at runtime
  • Thinking the two functions are ambiguous to Kotlin's overload resolution (they are not)
  • Suggesting you must rename the Kotlin-visible function name to fix it
  • Confusing this with an unresolved-reference or overload-resolution error

context

open as a page

Show how to resolve a generic-erasure platform clash for two overloads using @JvmName, and explain what each annotation changes.

level: middleimportance: must knowfreq 50%

basics

~10 s

Put @JvmName with a different name on at least one of the clashing functions. That gives each a unique method name in the compiled code, so the JVM no longer sees a duplicate.

open as a page

A Kotlin property's generated getter clashes with a function. Explain the mechanism and how to resolve it.

level: middleimportance: should knowfreq 35%

basics

~20 s

A Kotlin property quietly generates a getter method like getX(). If you also write a function named getX(), both produce the same JVM method, so they clash. Rename one with @JvmName, or rename the function.

open as a page

Can default arguments or @JvmOverloads cause platform declaration clashes? How do you reason about the generated signatures?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Yes. @JvmOverloads expands a function with default values into several Java overloads. If those generated overloads, after erasure, match an existing function's signature, you get a clash. You fix it by renaming with @JvmName or removing the overlap.

open as a page

How does erasure of inline value classes interact with signature clashes, and when does @JvmName help versus the automatic name mangling Kotlin already applies?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

An inline value class is erased to its underlying type on the JVM, so two functions taking different value classes over the same base type would clash. Kotlin auto-mangles their names to avoid that; @JvmName is for the generic-collection case it can't auto-fix.

open as a page