Mangled JVM names in public API
MANGLED_JVM_NAME_PUBLIC_API reports public functions, properties, and constructors that Java
sources can't call because a value class in their signature makes the JVM backend mangle the
compiled name.
| Diagnostic | MANGLED_JVM_NAME_PUBLIC_API |
| Default severity | Error |
| Applies to | JVM compilations only |
| Gradle property | mangledJvmNamePublicApi |
| Exemption | @IntentionallyMangledJvmName |
What it reports
A value class among the value parameters, the extension receiver, or the context parameters of a function or property - nullable types and type parameters bounded by a value class included.
@file:JvmName("Accounts")// Supporting value class/*** Stable identifier assigned to an account.** @property raw identifier as stored by the account service.*/@JvmInlinepublic value class AccountId(public val raw: String)/** Finds the account identified by [id]. */public fun find(id: AccountId) { }
Rationale
Value classes compile to their underlying type, so the backend needs a hashed suffix to keep the
compiled method distinct from an overload taking the underlying type directly - take(id: UserId)
compiles to take-4ZD5Yi0(...). A constructor gets no such suffix: the visible one becomes
private and a synthetic overload with a marker parameter takes its place. Kotlin callers resolve
by the source signature and never notice, but for Java the declaration is unreachable. See the
Kotlin guide on
inline value classes and mangling.
Don't
@file:JvmName("Users")// Compiles to take-4ZD5Yi0(...): an illegal Java identifier./** Queues a refresh for the account identified by [id]. */public fun take(id: UserId) { }
Do
@file:JvmName("Users")/** Queues a refresh for the account identified by [id]. */@JvmName("take")public fun take(id: UserId) { }
Don't
// The public constructor is replaced by// a private one and a synthetic marker-parameter overload./*** Wallet associated with a user account.** @property id identifier of the account that owns the wallet.*/@Pokopublic class Wallet(public val id: UserId)
Do
/*** Wallet associated with a user account.** @property id identifier of the account that owns the wallet.*/@OptIn(ExperimentalStdlibApi::class)@JvmExposeBoxed@Pokopublic class Wallet(public val id: UserId)
@JvmExposeBoxed generates Java-callable boxed variants alongside the mangled ones. It is the
only fix for constructors and overridable members, since @JvmName doesn't accept them.
Notes
- A value class inside a type argument (
List<UserId>) is boxed and keeps the JVM name. Only the classifier itself mangles, not a type it is nested in. - A top-level callable that merely returns a value class keeps its JVM name. The return type only mangles for members, where the dispatch receiver makes the difference.
- A
varproperty's setter mangles independently of the getter - renaming or hiding one accessor leaves the other checked on its own. - Members and constructors of the value class itself are not reported: declaring the public value class
is the deliberate choice, and
@JvmNameis not even applicable inside it. suspendfunctions are not reported: an unmangled name would not make them Java-callable anyway.- Overrides are not reported: their signature is fixed by the overridden declaration, which is reported instead.
@PublishedApi internaldeclarations are not reported: their public bytecode entry is a binary implementation detail, not supported Java source API.@JvmSyntheticdeclarations are hidden from Java on purpose and are not reported.- Non-JVM compilations never register this check at all.
Exemption
Apply @IntentionallyMangledJvmName when Java callers are not supported for this declaration:
@file:JvmName("Users")/** Queues a refresh for [id] through a deliberately Kotlin-only API. */@IntentionallyMangledJvmName(reason = ExemptionReason.API_DESIGN)public fun take(id: UserId) { }
Configuration
apiWatchdog {javaInterop {mangledJvmNamePublicApi = WatchdogSeverity.WARNING}}
The property lives inside the javaInterop { } block. javaInterop { enabled = false } turns off
this check along with the rest of the Java interop checks group.
With direct compiler invocation:
-P plugin:org.jetbrains.kotlin.library.api.watchdog:diagnosticSeverity=MANGLED_JVM_NAME_PUBLIC_API:warning
See also
- Inline value classes and mangling
- Java interop checks
- Kotlin-only API without JvmSynthetic, the sibling check for shapes that stay visible to Java but are not idiomatically callable
- Exemptions and internal API