Skip to content

SYSTEM Cited by 1 source

Kotlin binary compatibility validator

Definition

The Kotlin binary compatibility validator is a JetBrains- maintained Gradle plugin (github.com/Kotlin/binary-compatibility-validator) that captures a Kotlin library's public ABI as a check-in artefact and fails CI if a code change would alter that ABI in a way that breaks consumers compiled against the previous version.

The wiki's first canonical citation is via Airbnb's Viaduct 1.0 announcement, which names the validator as the CI gate enforcing the project's @StableApi / @ExperimentalApi / @InternalApi discipline. (Source: sources/2026-05-13-airbnb-viaduct-1-0-and-the-future-of-airbnbs-data-mesh)

What it does

The validator's mechanic is straightforward:

  1. Dump the public ABI. A Gradle task scans the project's compiled bytecode, extracts every public class, function, property, and constructor, and writes them to a .api file that lives in the repository (typically under api/).
  2. Diff in CI. On each build, the dump is regenerated from the current code and diffed against the checked-in .api file.
  3. Fail on drift. If the dump differs and the change isn't the expected one (i.e. the developer hasn't updated the .api file alongside the code change), the build fails.

This forces every ABI change to be explicit — the developer must regenerate and check in the new dump as part of the PR. Reviewers can then see the exact ABI delta in the PR diff.

Annotation-aware filtering

The validator can be configured to ignore elements annotated with project-specific markers — typically internal- use or experimental-use annotations. A common Kotlin/JVM configuration:

apiValidation {
    ignoredClasses.addAll(listOf(/* generated stuff */))
    nonPublicMarkers.addAll(listOf(
        "com.example.InternalApi",
        "com.example.ExperimentalApi"
    ))
}

This is the integration point with the @StableApi / @ExperimentalApi / @InternalApi pattern: only @StableApi elements (everything not annotated with the non-public markers) are tracked by the validator. ABI changes to elements marked non-stable don't fail the build; ABI changes to @StableApi elements do.

Why this matters at the OSS 1.0 line

Pre-1.0, breaking-change discipline can be informal. Post-1.0, the "don't break consumers compiled against the previous version" contract has to be machine-checkable, because:

  • Consumers are no longer in your monorepo where you can refactor them in lockstep.
  • Consumers' binaries (e.g. application JARs) are linked against a specific version of your library, and re-linking is a customer cost.
  • A typo'd refactor that accidentally changes a method signature is otherwise invisible until a downstream consumer builds against the new version and gets a NoSuchMethodError at runtime.

Viaduct's 1.0 announcement names the post-1.0 disclipline shift in those terms (verbatim): "Until now, Viaduct has evolved rapidly to meet internal needs, often with breaking changes managed through our internal monorepo tooling. Public release required a different approach."

Coverage limits

  • Bytecode level only. The validator catches ABI changes (signatures, modifiers, type parameters). It does not catch:
    • Behavioural changes to a method whose signature stays the same.
    • Changes to documented exception-throwing semantics.
    • Implicit ordering / sorting / iteration-order changes.
    • Changes to which IO / DB calls a method makes.
  • Java consumers see the JVM ABI. Kotlin features that desugar to JVM bytecode (data classes, default arguments, inline functions, sealed classes) are tracked at the desugared level. A change that's source-compatible from Kotlin but alters the desugared bytecode is still a breaking change for Java consumers and will fail the validator.
  • Generated code can be noisy. Kotlin Symbol Processing (KSP) / kapt-generated bytecode often shows up in the dump and needs explicit ignoredClasses filtering.

Adjacent tools

  • Java: revapi, japicmp, jdiff — the JVM ecosystem's Java-source-and-bytecode-comparison tools, all older than the Kotlin validator and all without first-class Kotlin understanding.
  • Rust: cargo-semver-checks — same shape (track public API as a check-in artefact, fail CI on unexpected change), newer.
  • Python: no widely-adopted equivalent at the package level; PEP 600 stable-ABI for CPython extensions covers a narrower scope.
  • C++: abi-compliance-checker — long-standing tool, more manual.

The Kotlin validator's specific value is its idiomatic Kotlin understanding + Gradle integration + annotation- based filter — none of the Java-era tools handle Kotlin's desugaring decisions or annotation conventions natively.

Seen in

Last updated · 542 distilled / 1,571 read