Java interop checks
library-api-watchdog includes six checks that keep a JVM library ergonomic for Java consumers. They flag
shapes that compile fine but that Java callers can't use idiomatically, or can't use at all. All
six only run in JVM compilations.
The checks
- Mangled JVM names in public API - a value class in a signature makes the JVM backend mangle the compiled name, so Java can't call it.
- Kotlin-only API without JvmSynthetic -
suspendfunctions, reified generics, and Kotlin-specific function types stay visible to Java even though Java can't call them idiomatically. - Companion API without JvmStatic - companion object functions compile to instance methods and are only reachable from Java through the companion instance getter.
- Companion constants without JvmField - constant-shaped companion properties are only reachable from Java through the companion instance getter.
- Top-level API without JvmName - a file's public top-level declarations compile into a facade class named after the file, so renaming the file breaks Java callers.
- Default parameters without JvmOverloads - default parameter values are a Kotlin-only feature.
Without
@JvmOverloadsJava callers must specify every argument.
Kotlin-only audience
These checks only pay off for libraries that support Java consumers. A library with a Kotlin-only audience turns off the whole group instead of demoting each check individually:
apiWatchdog {javaInterop {enabled = false}}
The enabled switch wins over the per-check severities configured inside the same javaInterop { }
block: once it is false, none of the six diagnostics run, no matter what their individual
severity properties are set to. See the Configuration for the full
list of severity properties.
Per-declaration exceptions
A library that generally supports Java can still let individual declarations sacrifice Java
ergonomics on purpose - for example, a coroutine-based API with no blocking bridge planned.
Acknowledge that in place with the matching @Intentionally* exemption annotation for the check,
using the IGNORE_JAVA_INTEROP reason and a description of why this declaration ignores Java
callers:
@file:JvmName("Refresh")/** Fetches the latest value. */@IntentionallyKotlinOnlyApi(reason = ExemptionReason.IGNORE_JAVA_INTEROP,description = "Coroutine-only API," +"no blocking or CompletableFuture bridge planned.",)public suspend fun refresh(): String = fetchLatest()
IGNORE_JAVA_INTEROP only categorizes the exemption. The description still has to state the
reason. See Exemptions and internal API for the full exemption model, including
EXEMPTION_WITHOUT_EXPLANATION.
See also
- Kotlin's Java-to-Kotlin interop guide for background on how Kotlin declarations compile for Java callers.
- Mangled JVM names in public API
- Kotlin-only API without JvmSynthetic
- Companion API without JvmStatic
- Companion constants without JvmField
- Top-level API without JvmName
- Default parameters without JvmOverloads