When working with type parameters in Kotlin, you might encounter JVM signature clashes. This post explains why these conflicts occur and how to resolve them.

Problem example 1: Extension functions with generic types

We can get into this problem when we add extension methods to a class with a type parameter (also called generics). Look at this post to see why we’d want to do this.

data class Coordinate<T>(val x: T, val y: T)

operator fun Coordinate<Int>.plus(other: Coordinate<Int>) =
    Coordinate(x + other.x, y + other.y)

operator fun Coordinate<Double>.plus(other: Coordinate<Double>) =
    Coordinate(x + other.x, y + other.y)

fun main() {
    println(Coordinate(0, 4) + Coordinate(1, 2))
    println(Coordinate(0.1, 0.5) + Coordinate(0.001, 0.4))
}

However, when we try to compile this code, we get the following error:

Kotlin: Platform declaration clash: The following declarations have the same JVM signature (plus(Lexample/Coordinate;Lexample/Coordinate;)Lexample/Coordinate;):
    fun Coordinate<Double>.plus(other: Coordinate<Double>): Coordinate<Double> defined in example
    fun Coordinate<Int>.plus(other: Coordinate<Int>): Coordinate<Int> defined in example

Problem example 2: Function overloading with generic parameters

It also happens when we use function overloading.

fun f(l: List<Int>): Int = 1

fun f(l: List<Double>): Int = 2

fun main() {
    println(f(listOf(1, 2)))
    println(f(listOf(0.1, 0.2)))
}
Kotlin: Platform declaration clash: The following declarations have the same JVM signature (f(Ljava/util/List;)I):
    fun f(l: List<Double>): Int defined in example
    fun f(l: List<Int>): Int defined in example

Why does this happen?

This issue occurs due to a concept called type erasure in the JVM. Here’s what happens:

  1. In Kotlin (and Java), generic type information exists only at compile time

  2. At runtime, the JVM erases the generic type parameters

  3. List<Int> and List<Double> both become just List at the JVM level

  4. Similarly, Coordinate<Int> and Coordinate<Double> both become just Coordinate

As a result, from the JVM’s perspective, our functions have identical signatures:

  • Both plus functions become plus(Coordinate, Coordinate): Coordinate

  • Both f functions become f(List): Int

The Kotlin compiler detects this conflict and prevents us from creating code that would be ambiguous at runtime.

Solution

Use @JvmName to manually give the functions different names:

data class Coordinate<T>(val x: T, val y: T)

@JvmName("CoordinatePlusInt")
operator fun Coordinate<Int>.plus(other: Coordinate<Int>) =
    Coordinate(x + other.x, y + other.y)

@JvmName("CoordinatePlusDouble")
operator fun Coordinate<Double>.plus(other: Coordinate<Double>) =
    Coordinate(x + other.x, y + other.y)

Similarly, for our second example:

@JvmName("fInt")
fun f(l: List<Int>): Int = 1

@JvmName("fDouble")
fun f(l: List<Double>): Int = 2
shadow-left