skip to content

You expose `fun pay(amount: Money)` where `@JvmInline value class Money(val cents: Long)`. A teammate says Java can't find `pay`. Explain what the compiler did to the signature and why.

level: middleimportance: should knowfreq 40%

answer

  1. value class erases to underlying type
  2. pay(Money) and pay(Long) would clash -> mangle
  3. hyphen + hashcode, e.g. pay-A1bC2dE
  4. hyphen is illegal Java identifier on purpose
  5. return-type-only value class also mangles

basics

~20 s

An inline value class disappears at runtime — it's replaced by its underlying type (here Long). To keep functions that take value classes apart from ones that take the raw type, Kotlin adds a hash suffix to the method name, so Java sees a mangled name instead of pay.

solid answer

~40 s

`@JvmInline value class Money(val cents: Long)` has no real object at runtime in most cases — the compiler **inlines** it, so `Money` is represented by its underlying `Long`. That means `fun pay(amount: Money)` and a hypothetical `fun pay(amount: Long)` would compile to the *same* JVM signature `pay(long)`, an illegal clash. To prevent this and to mark the function as 'uses a value class', the compiler **mangles the function name** with a stable hash suffix, e.g. `pay-<hashcode>` (the hyphen makes it an invalid Java identifier on purpose). Java therefore cannot call `pay` by name. To expose a clean Java-callable entry point, add `@JvmName("pay")` (and ensure no real clash). Mangling applies to functions whose parameter or receiver types are unsigned/value classes; the return type alone can also trigger it.

code

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

fun pay(amount: Money) { /* bytecode: pay-<hashcode>(J)V */ }

@JvmName("payExact")
fun payForJava(amount: Money) { /* bytecode: payExact(J)V, Java-callable */ }

go deeper

for a junior

Knows a value class is represented by its underlying type and that the function name looks mangled.

for a middle

Explains the erasure-induced clash and that a hyphen+hash suffix disambiguates; reaches for @JvmName.

for a senior

Covers receiver/return-type triggers, contrasts hyphen vs $module, and knows unsigned types are value classes too.

for a principal

Designs the Java-facing API: explicit @JvmName overloads or facade functions, weighing erasure boxing rules and binary compatibility.

## Inline value classes recap `@JvmInline value class Money(val cents: Long)` wraps exactly one value. The `@JvmInline` annotation tells the compiler to **erase the wrapper at runtime** wherever possible: a `Money` is represented directly by its underlying `Long`. This gives zero-allocation type safety. ## The signature problem Because `Money` erases to `Long`, a function taking `Money` and a function taking `Long` would produce the *identical* JVM descriptor: ``` pay(J)V // J = long ``` That is an illegal duplicate. Two distinct Kotlin functions cannot share one JVM signature. The compiler resolves this by **name mangling**: it appends a deterministic hash derived from the value-class types in the signature. ```kotlin @JvmInline value class Money(val cents: Long) fun pay(amount: Money) { /* ... */ } // bytecode: pay-<hashcode>(J)V e.g. pay-A1bC2dE(J)V ``` The **hyphen** (`-`) is chosen deliberately because it is **not a legal Java identifier character**, so Java source literally cannot reference the method. This is stronger than the `$module` suffix used for `internal` (which Java *can* type). ## What triggers value-class mangling - A **parameter** of value-class type. - A **receiver** of value-class type (extension/member on a value class). - A **return type** of value-class type — return-type-only differences also force mangling, since the JVM ignores return type for overload resolution and Kotlin needs to disambiguate. - The same applies to Kotlin's **unsigned types** (`UInt`, `ULong`, …), which are themselves inline value classes. ## Calling from Java Java cannot name a `pay-<hash>` method. Two options: ```kotlin @JvmName("pay") fun payJava(amount: Money) { /* ... */ } ``` or expose an overload that takes the underlying type. Note `@JvmName` removes mangling but you must avoid creating a real signature clash. ## Constructors and properties Properties returning a value class also get mangled getters. A value class's own constructor and `box`/`unbox` helpers are generated by the compiler; calling them from Java is awkward and usually not intended. ## Key takeaway Value-class mangling (hyphen + hash) protects against erasure-induced signature collisions; `internal` mangling (`$module`) protects module boundaries. Different suffix style, different purpose.

  • Why is a hyphen used in the value-class mangle but a `$` in the internal mangle?
    The hyphen is an invalid Java identifier, hard-blocking Java calls; `$` is valid in Java identifiers, so internal members stay technically callable. Different intents.
  • Does returning a value class (not just taking one) cause mangling?
    Yes. Since the JVM ignores return type for overloads, Kotlin mangles return-type-only value-class differences to keep them distinct.

Money is a label sticker on a Long; once you peel it off at runtime, two functions look identical, so the compiler stamps a serial number to tell them apart.

saying these in an interview costs you the question

  • Claiming `@JvmInline` value classes are always boxed objects at runtime
  • Saying only parameters (never return types) trigger mangling
  • Thinking the hyphen suffix is the same mechanism as the internal `$module` suffix
  • Asserting Java can call the hyphenated method by typing the hyphen

context