Kotlin-only API without JvmSynthetic
KOTLIN_ONLY_API_WITHOUT_JVM_SYNTHETIC reports public functions whose shape only Kotlin callers
can use idiomatically, while the function still lands in the API surface Java sources see.
| Diagnostic | KOTLIN_ONLY_API_WITHOUT_JVM_SYNTHETIC |
| Default severity | Error |
| Applies to | JVM compilations only |
| Gradle property | kotlinOnlyApiWithoutJvmSynthetic |
| Exemption | @IntentionallyKotlinOnlyApi |
What it reports
Three shapes trigger it:
- A
suspendfunction (Java sees a trailingContinuationparameter it can't provide idiomatically) - An
inlinefunction with areifiedtype parameter (calling the compiled method from Java fails at runtime) - A function taking a Kotlin-specific function type - a suspend function type, a
function type with receiver, or a
Unit-returning function type
@file:JvmName("Loading")/** Loads the value identified by [key]. */public suspend fun load(key: String) { }
Rationale
A Kotlin-only shape still compiles a method Java sources can see and try to call, even though
Java can't use it the way Kotlin callers do, or can't use it at all. Leaving it visible without
comment misleads Java-facing API browsing and, for a reified type parameter, produces a call
that compiles in Java but fails at runtime. See Kotlin's
Java-to-Kotlin interop guide for how
these shapes actually compile.
Don't
@file:JvmName("KotlinOnly")// Java sees a trailing Continuation parameter// it can't provide idiomatically.///** Refreshes [key]. */public suspend fun refresh(key: String) { }// Only inlining Kotlin call sites can substitute T.// Calling the compiled method from Java fails at runtime.///** Creates an instance of [T]. */public inline fun <reified T> instantiate(): T? = null// A Java lambda has to return the Unit.INSTANCE token explicitly.///** Invokes [action] for each value. */public fun onEach(action: (Int) -> Unit) { }
Do
@file:JvmName("KotlinOnly")/** Refreshes [key]. */@JvmSyntheticpublic suspend fun refresh(key: String) { }/** Creates an instance of [T]. */@JvmSyntheticpublic inline fun <reified T> instantiate(): T? = null/** Consumes values produced by an iteration. */@IntentionallyOpen(reason = ExemptionReason.API_DESIGN)public fun interface Action {/** Processes [value] emitted by the iteration. */public fun doAction(value: Int)}/** Invokes [action] for each value. */public fun onEach(action: Action) { }
@JvmSynthetichides the Kotlin-only member from Java entirely. (Asuspendfunction can instead ship alongside a blocking orCompletableFuture-returning bridge for Java callers.)- A
fun interfaceparameter gives Java a lambda-friendly type instead of a Kotlin function type.
Notes
- Abstract and interface members are not reported:
@JvmSyntheticcan't hide a member that implementations must provide. - Overrides are not reported: their shape is fixed by the overridden declaration, which is reported instead.
- Constructors are not reported:
@JvmSyntheticdoesn't apply to them. - A signature mangled by a value class is reported by
MANGLED_JVM_NAME_PUBLIC_APIinstead. @PublishedApi internalfunctions 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 @IntentionallyKotlinOnlyApi to the function, or to an enclosing class to cover every
function inside, when leaving the Kotlin-only shape visible to Java is intended:
@file:JvmName("KotlinOnly")/** Refreshes [key] through a deliberately Kotlin-only API. */@IntentionallyKotlinOnlyApi(reason = ExemptionReason.API_DESIGN)public suspend fun refresh(key: String) {}/** Creates an instance of [T] through a deliberately Kotlin-only API. */@IntentionallyKotlinOnlyApi(reason = ExemptionReason.API_DESIGN)public inline fun <reified T> instantiate(): T? = null/** Invokes [action] through a deliberately Kotlin-only API. */@IntentionallyKotlinOnlyApi(reason = ExemptionReason.API_DESIGN)public fun onEach(action: (Int) -> Unit) { }
Configuration
apiWatchdog {javaInterop {kotlinOnlyApiWithoutJvmSynthetic = 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=KOTLIN_ONLY_API_WITHOUT_JVM_SYNTHETIC:warning