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?
answer
- internal = module-scoped, JVM has no such level
- compiler emits public + $module suffix
- computeScore$app_main
- only members mangled, not classes
- @JvmName gives a stable Java name
basics
~20 sKotlin'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, 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
Recognizes the $module suffix and knows internal is module-scoped; can name @JvmName as the escape hatch.
Explains why the JVM lacks an internal level and that the member is public-but-mangled, callable but fragile.
Distinguishes members (mangled) from classes (not), and weighs signature-clash avoidance as the design motive.
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