skip to content

Binary Compatibility Validator

The binary compatibility validator dumps your public ABI to a checked-in file and fails the build when a change would break existing consumers. It is the tool that turns 'we did not mean to break the API' into a compile error.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

Explain the relationship between the apiDump and apiCheck tasks and the typical developer workflow when you intentionally change a public API.

level: middleimportance: must knowfreq 40%

answer

  1. apiCheck = gate (in `check`), apiDump = update
  2. apiDump is manual/intentional, never auto
  3. Removed line = breaking; added line = additive
  4. Commit code + .api together
  5. apiCheckAll/apiDumpAll aggregate multi-module

basics

~20 s

apiCheck compares your current public API to the saved file and fails if they differ. apiDump regenerates that saved file. When you intentionally change the API, you run apiDump and commit the updated file so the check passes again.

solid answer

~40 s

`apiCheck` is the *gate*: it recomputes the public ABI from compiled classes and diffs it against the committed `.api`; any mismatch fails the build, and it runs as part of `check`. `apiDump` is the *update*: it overwrites the `.api` with the current surface. The loop is: change a public declaration → `apiCheck` fails with a diff → if the change is intentional, run `./gradlew apiDump` → review the resulting `.api` diff (a removed line is a potential breaking change; an added line is a new API) → commit code + `.api` together. The key discipline: `apiDump` is a deliberate, human-triggered action, never automatic, so that every API delta is consciously approved. Reviewers gate breaking removals/changes in the PR via the `.api` diff. In multi-module builds, each module has its own `apiCheck`/`apiDump` plus aggregate `apiCheckAll`/`apiDumpAll`.

go deeper

for a junior

Knows apiCheck fails and apiDump fixes it, even if hazy on why dump is manual.

for a middle

Articulates the full loop, why apiDump is deliberate, and reads a .api diff as breaking vs additive.

for a senior

Ties the diff to SemVer decisions and PR review gating; knows multi-module aggregate tasks.

for a principal

Designs the team process: who approves API deltas, how .api diffs map to release policy, and CI enforcement so apiDump can never be a rubber stamp.

## Two tasks, two roles | Task | Role | When run | |------|------|----------| | `apiCheck` | **Verification gate** — recomputes the public ABI and fails on any diff vs the committed `.api`. Depended on by `check`. | Every CI build / `./gradlew check` | | `apiDump` | **Snapshot update** — regenerates the `.api` from current code. | Manually, only when you intend to change the API | ## Why apiDump is manual If the dump regenerated automatically on every build, breaking changes would silently "pass" because the baseline would always be rewritten to match. Keeping `apiDump` a **deliberate, human-invoked** step is the whole point: an API change must be *consciously approved* and committed, leaving a reviewable diff. ## The workflow ```text 1. Edit code, e.g. rename a public function or change a parameter type. 2. ./gradlew apiCheck # FAILS, prints the offending diff 3. Decide: - accidental? -> revert the code change. - intentional? -> ./gradlew apiDump 4. git diff api/ # eyeball the API delta - a removed/changed line = potential BREAKING change (bump major in SemVer) - an added line = additive (minor) 5. Commit code + updated .api together. ``` ## Reading an .api diff A `.api` line records a JVM signature. A change like ```diff - public final fun parse (Ljava/lang/String;)Lcom/acme/Result; + public final fun parse (Ljava/lang/String;Z)Lcom/acme/Result; ``` shows a parameter was added — binary-breaking, because existing compiled callers reference the old descriptor. Reviewers treat removals/changes as red and weigh them against the version bump. ## Multi-module builds Each subproject gets its own `apiCheck`/`apiDump`. The root project also exposes aggregate `apiCheckAll` and `apiDumpAll` so you can verify/regenerate the whole tree at once. Each module's `.api` lives under that module's `api/` directory. ## Common pitfall Forgetting to run `apiDump` after an intentional change makes CI red; running it *without reviewing* the diff defeats the purpose — always inspect what changed.

  • Why shouldn't apiDump run automatically as part of every build?
    Because the baseline would always be rewritten to match the code, so breaking changes would silently pass. Manual apiDump forces a reviewable, deliberate API delta.
  • In a multi-module project, how do you regenerate all dumps at once?
    Run the aggregate `apiDumpAll` task from the root; each module also has its own `apiDump` and `apiCheck`.

saying these in an interview costs you the question

  • Saying apiDump runs automatically on every build
  • Running apiDump blindly without reviewing the .api diff
  • Not committing the .api change alongside the code
  • Thinking apiCheck modifies files (it only reads/compares)
  • Confusing which task is wired into `check`

context

open as a page

What is the kotlinx binary-compatibility-validator, and what problem does it solve for a Kotlin library?

level: juniorimportance: should knowfreq 35%

basics

~20 s

It is a tool that records your library's public API into text files. If a change accidentally removes or alters that public API, the build fails, warning you before you break code that depends on your library.

open as a page

How do you wire BCV into a Gradle library so CI enforces it, and how do you exclude an experimental package or a generated class from the dump?

level: middleimportance: should knowfreq 25%

basics

~20 s

Apply the BCV Gradle plugin; it adds apiCheck to the standard check task, so any CI running ./gradlew check enforces it. To exclude things, use the apiValidation block to ignore packages, classes, or annotation markers.

open as a page

What exactly ends up in a .api dump, and what Kotlin constructs are excluded or need special handling (e.g. internal, @PublishedApi, inline)?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The dump records everything reachable through your public API as JVM signatures: public and protected classes, methods, and fields. Truly internal or private things are excluded. Some inline-related members still leak into the binary API and show up.

open as a page

A teammate argues 'apiCheck passed, so this release is safe.' Where does BCV's guarantee actually end, and what kinds of breaking changes can still slip through?

level: principalimportance: should knowfreq 22%

basics

~20 s

A passing apiCheck only means the recorded public API signatures didn't change. It can't see changes in behavior, inlined function bodies, constant values, or runtime contracts — so a release can still break consumers even when the check is green.

open as a page