skip to content

Name Mangling (internal & value classes)

internal members and functions with value-class parameters get a hash suffix in their JVM names, so Java cannot call them under the Kotlin name. @JvmName is the fix, and knowing why the mangling exists is the interesting half.

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

questions

5

A Java class in the same module-graph tries to call a Kotlin function declared `internal fun computeScore()`, but autocomplete shows a weird name like `computeScore$app_main`. What is happening and why?

level: juniorimportance: should knowfreq 45%

answer

  1. internal = module-scoped, JVM has no such level
  2. compiler emits public + $module suffix
  3. computeScore$app_main
  4. only members mangled, not classes
  5. @JvmName gives a stable Java name

basics

~20 s

Kotlin's internal means 'visible only inside this module'. To keep Java from calling it casually, Kotlin renames it in the compiled bytecode by adding a suffix, so the Java-visible name no longer matches the Kotlin name.

solid answer

~40 s

`internal` is module-scoped visibility in Kotlin, but the JVM has no equivalent — its closest level is `public`. To prevent Java code (which ignores Kotlin's `internal`) from accidentally calling these members and to avoid signature clashes across modules, the Kotlin compiler emits internal members as `public` in bytecode but mangles their names with a `$<module-name>` suffix (e.g. `computeScore$app_main`). Java sees only the mangled name. Java can technically still call it via the mangled name (since the JVM method is public), but Kotlin treats it as a non-API and the name can change. To expose an internal member with a stable Java-callable name you annotate it with `@JvmName("computeScore")`. The mangling does NOT apply to internal classes — only members (functions/properties).

code

kotlin · 5 lines
kotlin
// Kotlin, module 'app', source set 'main'
internal fun computeScore(x: Int): Int = x * 2   // -> computeScore$app_main in bytecode

@JvmName("computeScore")
internal fun computeScoreStable(x: Int): Int = x * 2 // -> computeScore (Java-callable)

go deeper

for a junior

Recognizes the $module suffix and knows internal is module-scoped; can name @JvmName as the escape hatch.

for a middle

Explains why the JVM lacks an internal level and that the member is public-but-mangled, callable but fragile.

for a senior

Distinguishes members (mangled) from classes (not), and weighs signature-clash avoidance as the design motive.

for a principal

Frames mangling as part of binary-compatibility/API-surface policy and advises team conventions for Java/Kotlin mixed modules.

## What `internal` means Kotlin has four visibility modifiers: `public` (default), `private`, `protected`, and `internal`. `internal` means the declaration is visible **everywhere inside the same compilation module** (e.g. one Gradle source set / IntelliJ module) but invisible to other modules. ## Why the JVM forces a workaround The JVM bytecode model only has `public`, `protected`, package-private (default), and `private`. There is **no 'module-internal' access level**. The Kotlin compiler must map `internal` onto one of these. It chooses **`public`** in bytecode (package-private wouldn't work across packages within the same module). But emitting `internal` as plain `public` would let Java code in any module call it freely and could cause **signature clashes** when two modules each define an `internal fun foo()`. The solution is **name mangling**: the compiler appends a `$<module-name>` suffix to the JVM method name. ```kotlin // Kotlin (module 'app', main source set) internal fun computeScore(x: Int): Int = x * 2 ``` Compiles to bytecode roughly equivalent to: ```java // Java view (decompiled) public static int computeScore$app_main(int x) { return x * 2; } ``` ## Consequences - **From Kotlin (same module):** you call `computeScore(...)` normally — the compiler knows the mangled name. - **From Java:** you only see `computeScore$app_main`. You *can* call it (it's public), but it's fragile: the module name is part of the suffix and changes if the module is renamed. - **Internal classes are NOT mangled** — only members. An `internal class` becomes a public class with its plain name. ## The fix: `@JvmName` ```kotlin @JvmName("computeScore") internal fun computeScore(x: Int): Int = x * 2 ``` Now Java sees a stable, unmangled `computeScore` method. Use this only when you deliberately want Java to call an internal member. ## Key takeaway The suffix is a deliberate barrier, not a bug. It signals 'this is not part of the cross-module API surface'.

  • Is the mangled internal method actually callable from Java?
    Yes — it's emitted as a public JVM method, so Java can call it via the mangled name. It's just not intended as API and the suffix can change.
  • Does an `internal class` also get mangled?
    No. Only members (functions and properties) get the suffix; classes keep their plain name and become public.

It's like a staff-only door that's technically unlocked but has a confusing label, so outsiders don't wander through it.

saying these in an interview costs you the question

  • Saying `internal` is compiled as `private` in bytecode
  • Claiming Java literally cannot call it (it can, via the mangled name)
  • Thinking the JVM has a native 'internal'/'module' access level
  • Believing internal classes are also mangled

context

open as a page

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%

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.

open as a page

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%

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.

open as a page

Your build sets a custom Gradle module name, and later a Java module that referenced a mangled `internal` method by its `$module` name fails to link. Explain the binary-compatibility hazard and how mangling factors in.

level: seniorimportance: should knowfreq 18%

basics

~20 s

The suffix on an internal method contains the module's name. If the module name changes, the mangled method name changes too, so anything that linked against the old name breaks. Internal members are not stable API — don't rely on their JVM names.

open as a page

You're designing a Kotlin library consumed by both Kotlin and Java teams, using `@JvmInline value class` domain types and `internal` helpers. How do you architect the public surface so mangling never bites Java consumers?

level: principalimportance: nice to knowfreq 12%

basics

~20 s

Keep the value classes for Kotlin callers, but give Java a separate, plain-typed public API. Don't let Java touch internal members or value-class-typed methods directly; expose facade functions that take primitive/boxed types and have stable names.

open as a page