skip to content

You want a `internal` Kotlin helper and a value-class-parameter function both callable from a Java module with clean names. How do you remove the mangling, and what risk does that introduce?

level: middleimportance: should knowfreq 30%

answer

  1. @JvmName overrides both $module and -hash suffixes
  2. use @get:JvmName / @set:JvmName for accessors
  3. risk: platform declaration clash (same descriptor)
  4. doesn't change Kotlin visibility — internal stays internal
  5. prefer a public facade over JvmName-ing internals

basics

~20 s

Add @JvmName("cleanName") to the function. That overrides the compiler's mangled name with the one you pick, so Java sees a normal method. The risk is you might now collide with another method that has the same JVM signature.

solid answer

~40 s

`@JvmName("x")` sets the exact JVM method name, bypassing both the `$module` suffix (internal) and the `-<hash>` suffix (value classes). For an internal member it makes a stable Java-callable name; for a value-class function it removes the hyphenated name. The **risk**: mangling existed to prevent signature clashes. By forcing a plain name you can create a genuine duplicate JVM signature — e.g. `@JvmName("pay") fun pay(m: Money)` next to `fun pay(n: Long)` both become `pay(J)V`, which fails to compile ('platform declaration clash'). You must ensure no other function erases to the same descriptor. Also, exposing an `internal` member to Java with `@JvmName` widens your real API surface even though Kotlin still treats it as internal — a maintenance trap. Prefer dedicated facade/public functions over `@JvmName`-ing internals.

code

kotlin · 10 lines
kotlin
@JvmInline value class Money(val cents: Long)

fun pay(n: Long) { }

@JvmName("pay")            // ERROR: platform declaration clash with pay(Long)
fun pay(amount: Money) { }

// Fix: choose a distinct name
@JvmName("payMoney")
fun payMoney(amount: Money) { }

go deeper

for a junior

Knows @JvmName renames the JVM method to a clean name Java can call.

for a middle

Applies it to both internal and value-class cases and anticipates the platform-declaration-clash error.

for a senior

Uses @get:/@set:JvmName, knows visibility is unchanged, and prefers facades/underlying-type overloads.

for a principal

Sets policy: avoid leaking internals via @JvmName, document Java-facing surface, and guard against accidental signature collisions in CI.

## The escape hatch `@JvmName("theName")` is a Kotlin annotation that **renames the generated JVM method/field**. It is the single tool that removes name mangling in both scenarios: ```kotlin @JvmName("computeScore") internal fun computeScore(x: Int): Int = x * 2 // Java sees computeScore, not computeScore$app_main @JvmInline value class Money(val cents: Long) @JvmName("pay") fun pay(amount: Money) { } // Java sees pay, not pay-<hash> ``` ## Why mangling existed — and the risk of undoing it Mangling guaranteed **unique JVM signatures**. Removing it can reintroduce a clash: ```kotlin fun pay(n: Long) { } @JvmName("pay") fun pay(amount: Money) { } // Money erases to long -> both are pay(J)V ``` This produces a compile error: **'Platform declaration clash: two declarations have the same JVM signature'**. So `@JvmName` shifts responsibility for uniqueness onto you. ## Where `@JvmName` does and doesn't apply - Works on **top-level functions, member functions, and getters/setters** (use `@get:JvmName` / `@set:JvmName` for property accessors). - It does **not** change Kotlin-side visibility: an `internal` function annotated with `@JvmName` is still `internal` to Kotlin callers but now has a tidy public JVM name — a subtle leak. - Cannot be applied to constructors. ## Better patterns - For Java consumption of internal logic, write an explicit **public facade** function rather than `@JvmName`-ing an internal one — intent is clearer and Kotlin visibility stays honest. - For value classes, provide an **overload that takes the underlying type** (`fun pay(cents: Long)`) when Java should not deal with the wrapper at all. ## Property accessors ```kotlin val status: Status @JvmName("getStatus") get() = ... ``` For a value-class-typed property the getter is mangled; `@get:JvmName` gives Java a normal accessor. ## Key takeaway `@JvmName` is precise but blunt: it removes the safety net mangling provided, so you must personally verify there's no real JVM-signature collision.

  • Does `@JvmName` change the visibility seen by Kotlin callers?
    No. An `internal fun` stays internal to Kotlin; `@JvmName` only changes the JVM method name, which can leak it to Java despite the Kotlin visibility.
  • How do you rename a property's getter for Java?
    Use `@get:JvmName("getX")` (and `@set:JvmName` for the setter) on the property or accessor, since `@JvmName` must target the accessor.

saying these in an interview costs you the question

  • Believing `@JvmName` also makes an internal member `public` to Kotlin
  • Forgetting it can cause a 'platform declaration clash'
  • Trying to put `@JvmName` on a constructor
  • Using `@JvmName` directly on a property instead of `@get:`/`@set:`

context