A Kotlin K2 compiler plugin that warns library authors about public API declarations that are hard to evolve.
Documentation - a full write-up for every check: rationale, do/don't examples, exemptions, and configuration. The API reference covers the exemption annotations.
Add the snapshot repository to settings.gradle.kts for both plugin and library resolution:
pluginManagement {
repositories {
maven("https://packages.jetbrains.team/maven/p/kt-lib/eap")
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
maven("https://packages.jetbrains.team/maven/p/kt-lib/eap")
mavenCentral()
}
}The plugin only runs in modules compiled with explicit API mode (strict or warning variant). Without it the checkers are not registered at all, and the Gradle plugin prints a warning.
plugins {
kotlin("library.api-watchdog") version "<version>"
}
kotlin {
explicitApi()
}Applying the Gradle plugin registers the compiler plugin for every compilation except test
compilations, whose sources are never published, and automatically
adds the org.jetbrains.kotlin:kotlin-library-api-watchdog-plugin-annotations dependency with
the @Intentionally* exemption annotations. The
plugin is intentionally restrictive by default: every check reports a compilation error until it
is individually demoted to a warning or disabled through the apiWatchdog extension:
import org.jetbrains.kotlinx.libs.api.watchdog.WatchdogSeverity
apiWatchdog {
undocumentedPublicApi = WatchdogSeverity.WARNING
javaInterop {
enabled = false // A Kotlin-only library can drop the whole Java interop group.
}
}See Setup and the Gradle plugin reference for all options, including direct compiler invocation without Gradle.
Declarations that are public for technical reasons only are excluded from all checks by marking
the library's internal-API annotation with @InternalAnnotationMarker. A single declaration is
exempted in place with the matching @Intentionally* annotation, which must explain itself
through an ExemptionReason and a description. See
Exemptions and internal API.
A library that has already shipped can't change the shape of its public API without breaking
users, so the watchdog's first run typically floods it with diagnostics that are not actionable
anymore. The Gradle plugin registers an updateBackwardsCompatibilityExempts task that
acknowledges all of them in one sweep:
./gradlew updateBackwardsCompatibilityExemptsThe task depends on the regular main Kotlin compilation tasks for every KGP target, including JS,
Native, Wasm, and metadata-only projects. Those compilations record diagnostics with their exact
source positions. The task merges and deduplicates the reports, then inserts the matching
@Intentionally* annotations with reason = ExemptionReason.FOR_BACKWARDS_COMPATIBILITY (adding
imports as needed). A markerless @SubclassOptInRequired is replaced by @IntentionallyOpen
rather than annotated, since it restricts nothing to begin with. Checks disabled through
apiWatchdog are not exempted, and the few diagnostics no annotation can acknowledge are listed
as warnings for manual follow-up. Enabled always-error diagnostics must be fixed before the task
can run. Run it on a clean
working tree and review the diff. From then on the checks only guard newly added API. See the
existing-library guide for details.
OPEN_API_WITHOUT_SUBCLASS_OPT_IN- open/abstract classes and interfaces that can be subclassed outside the library without restriction.SUBCLASS_OPT_IN_WITHOUT_MARKERS-@SubclassOptInRequiredannotations that list no marker classes and so restrict nothing.EXHAUSTIVE_PUBLIC_API- enums and sealed hierarchies, which users can match exhaustively, so adding an entry or a subtype later breaks them.UNDOCUMENTED_PUBLIC_API- public declarations of every kind without KDoc.FUNCTION_TYPE_ALIAS_PUBLIC_API- type aliases of function types. The alias is erased from the compiled API, unlike afun interface.DATA_CLASS_PUBLIC_API- data classes, whose generatedcopy/componentNbake the exact property list into the compiled API.STATEFUL_CLASS_WITHOUT_EQUALS,STATEFUL_CLASS_WITHOUT_HASH_CODE, andSTATEFUL_CLASS_WITHOUT_TO_STRING- stateful classes relying on identity equality, identity hashing, or opaque rendering.MUTABLE_COLLECTION_PUBLIC_API- mutable collection types (arrays included) in public signatures.PAIR_OR_TRIPLE_PUBLIC_API-PairandTriplein public signatures. Tuple components carry no domain meaning.BOOLEAN_PARAMETER_PUBLIC_API- Boolean parameters of public functions, context parameters included. A baretrue/falsereveals nothing at the call site.NULLABLE_BOOLEAN_PUBLIC_API-Boolean?in public signatures: three states with only two of them named.REQUIRED_PARAMETER_AFTER_OPTIONAL- required parameters declared after optional ones.INCONSISTENT_PARAMETER_ORDER_IN_OVERLOADS- same-named parameters ordered differently across overloads, inviting silently swapped arguments.INLINE_FUNCTION_WITH_LOGIC- public inline bodies that do more than delegate. The logic freezes into user binaries.PUBLIC_TYPE_FROM_NON_TRANSITIVE_DEPENDENCY- public signatures using dependency types that are not published transitively to consumers. Always an error while enabled.PUBLIC_TYPE_WITH_INTERNAL_API- supported public signatures exposing types marked as internal API. Mark the exposing declaration as internal API too, or remove the internal type from its signature. Always an error while enabled.
These checks only run in JVM compilations. A Kotlin-only library disables the group with
javaInterop { enabled = false }. See the
group overview.
MANGLED_JVM_NAME_PUBLIC_API- value classes in signatures mangle the compiled JVM name out of Java's reach.KOTLIN_ONLY_API_WITHOUT_JVM_SYNTHETIC- suspend/reified/Kotlin-function-type shapes left visible to Java sources.COMPANION_API_WITHOUT_JVM_STATIC- companion functions Java can only reach asOuter.Companion.member(...).COMPANION_CONSTANT_WITHOUT_JVM_FIELD- constant-shaped companionvals readable from Java only through the companion instance.TOP_LEVEL_API_WITHOUT_JVM_NAME- file facades without@file:JvmName, leaking the file name into the Java API surface.DEFAULT_PARAMETERS_WITHOUT_JVM_OVERLOADS- for functions with default parameter values without@JvmOverloadsJava callers must pass every argument.
DSL_MARKER_NOOP_TARGET-@Targetentries on which a@DslMarkerannotation has no effect.DSL_MARKER_WITHOUT_EXPLICIT_TARGETS- DSL markers relying on the default target set, which allows nine no-op targets and forbids the effective ones.DSL_MARKER_NOOP_TYPE_POSITION- DSL markers written on type positions where receiver scope control ignores them.
EXEMPTION_WITHOUT_EXPLANATION-@Intentionally*exemptions whose reason and description explain nothing. Always an error, not configurable.
Performed by the Gradle plugin rather than the compiler:
- Explicit API mode warning - warns when explicit API mode is not enabled, since the watchdog registers no checks without it.
- Binary compatibility validation suggestion - warns when neither the Kotlin Gradle plugin's built-in ABI validation nor the standalone Binary Compatibility Validator is enabled.
The build resolves compiler-plugin-dev-kit from its
Space EAP repository.
The dev-kit settings plugin supplies its Kotlin development repositories, aligned plugin
coordinates, and functional-test publishing setup. The supported Kotlin versions, companion
library, and compiler-plugin project are declared through the dev-kit's namespaced properties in
gradle.properties.
Tests:
./gradlew :kotlin-library-api-watchdog-compiler-plugin:test # diagnostics tests
./gradlew :kotlin-library-api-watchdog-compiler-plugin:generateTests # regenerate JUnit classes from test data
./gradlew :kotlin-library-api-watchdog-gradle-plugin:functionalTest # Gradle integration testsModules:
:kotlin-library-api-watchdog-compiler-plugin- the compiler plugin (FIR checkers only). Test data lives in compiler-plugin/src/test/data/diagnostics.:kotlin-library-api-watchdog-plugin-annotations- the@Intentionally*exemption annotations,@InternalAnnotationMarker, and theExemptionReasonenum.:kotlin-library-api-watchdog-gradle-plugin- applies the compiler plugin and the annotations dependency (plugin idorg.jetbrains.kotlin.library.api-watchdog).
The documentation site is a Docusaurus project in docs, built by docs.yml. See docs/authoring.md for the page template and rules.