skip to content

A teammate marks a top-level extension `internal` and expects to call it from Java, and separately wants several files' extensions under one Java class. Walk through what actually happens and how to do it correctly.

level: seniorimportance: nice to knowfreq 20%

answer

  1. internal → public bytecode + mangled name suffix
  2. Don't expose internal extensions to Java
  3. Same @file:JvmName + @file:JvmMultifileClass = merge files
  4. Same JvmName WITHOUT multifile = duplicate-class error
  5. public is the default for Java-callable APIs

basics

~10 s

Internal extensions get a mangled, hard-to-call name from Java, so don't expect clean access. To group several files' top-level functions under one Java class, give each file the same @file:JvmName plus @file:JvmMultifileClass.

solid answer

~40 s

An `internal` top-level extension is compiled as `public` on the bytecode but with a **name-mangled** suffix (e.g. `shout$module_name`), because `internal` has no JVM equivalent. So Java can technically reach it but only via the ugly mangled name — treat internal as 'not part of the Java API'. To consolidate extensions from multiple files into one Java-visible facade, each file needs the **same** `@file:JvmName("Strings")` AND `@file:JvmMultifileClass`; without the multifile annotation, identical `@JvmName` values across files cause a duplicate-class error. The result is one `Strings` class aggregating all those static methods. Use `public` (the default) for anything Java should call; reserve `internal` for module-private helpers.

code

kotlin · 7 lines
kotlin
@file:JvmName("Strings")
@file:JvmMultifileClass
package com.app

fun shout(s: String) = s.uppercase()
internal fun helper() = Unit  // Java sees helper$module — avoid relying on it
// Java: Strings.shout("hi");

go deeper

for a junior

Knows internal is for module visibility and that files map to facade classes by default.

for a middle

Explains name mangling for internal and the basic @file:JvmName rename.

for a senior

Correctly combines @file:JvmName with @file:JvmMultifileClass, predicts the duplicate-class error, and advises against exposing internal to Java.

for a principal

Sets module/package conventions so the Java-facing facade is stable and intentional, avoiding accidental mangled-name dependencies across module boundaries.

## Part 1 — `internal` and Java Kotlin's `internal` means *visible within the same compilation module*. The JVM has **no `internal` access level**, so the compiler emits the member as **`public` bytecode** but **mangles the name** to discourage external use: ``` fun internal shout() -> shout$app_main // suffix derived from the module name ``` Consequences: - Kotlin in the same module calls `shout()` normally. - Kotlin in another module **cannot** see it (compiler enforces `internal`). - **Java** can call it, but only via the mangled name `ShoutKt.shout$app_main(...)`, which is brittle and module-name-dependent. So *expecting clean Java access to an internal extension is wrong* — make it `public`. ## Part 2 — one Java class from many files By default each `.kt` file gets its **own** facade (`AKt`, `BKt`, ...). To merge: ```kotlin // file A.kt @file:JvmName("Strings") @file:JvmMultifileClass package com.app fun shout(s: String) = s.uppercase() ``` ```kotlin // file B.kt @file:JvmName("Strings") @file:JvmMultifileClass package com.app fun whisper(s: String) = s.lowercase() ``` Now Java sees a single class: ```java Strings.shout("hi"); Strings.whisper("HI"); ``` The compiler generates a multi-file facade plus per-file `__StringsKt` part classes under the hood; you only interact with `Strings`. ### Failure mode If you give two files the **same** `@file:JvmName` but **omit** `@file:JvmMultifileClass`, you get a **duplicate class / clash** compile error, because each file independently tries to own the `Strings` class. ## Practical guidance - Java-facing extensions: keep `public`, pick a clear `@file:JvmName`, group with `@file:JvmMultifileClass`. - Module-private helpers: `internal` (and accept they're not Java API). - Don't rely on mangled names; they change with the module name and are not a stable contract.

  • Why can Java technically call an internal Kotlin function at all?
    The JVM has no internal access level, so Kotlin emits it as public bytecode; it just mangles the name to deter use from outside the module.
  • What breaks if two files share @file:JvmName but lack @file:JvmMultifileClass?
    A duplicate/clashing class compile error, because each file tries to generate the same facade class independently.

saying these in an interview costs you the question

  • Thinks internal completely hides a member from Java at the bytecode level
  • Relies on the mangled name as a stable Java contract
  • Believes same @file:JvmName across files merges automatically without @JvmMultifileClass
  • Confuses @file:JvmName (class name) with @JvmName (method name) in this scenario

context