skip to content

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%

answer

  1. Apply plugin in root; apiCheck auto-joins `check`
  2. First run: apiDump then commit api/
  3. CI just runs ./gradlew check
  4. apiValidation: ignoredPackages / ignoredClasses / nonPublicMarkers
  5. nonPublicMarkers = clean experimental carve-out

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.

solid answer

~40 s

Apply `id("org.jetbrains.kotlinx.binary-compatibility-validator")` in the root build. The plugin auto-registers `apiCheck` as a dependency of `check`, so a CI step running `./gradlew check` (or `apiCheck`/`apiCheckAll`) fails on signature drift — no extra wiring needed. Commit the generated `api/*.api` files. To shape the surface, configure the `apiValidation { }` extension: `ignoredPackages` (drop a whole package, e.g. an `internal` impl package), `ignoredClasses` (drop a specific generated class), `nonPublicMarkers` (treat anything annotated with your `@InternalApi`/experimental marker as non-public so it stays out of the contract), and `validationDisabled`/`ignoredProjects` for opting modules out. Typical CI: developers run `apiDump` locally and commit the `.api`; CI runs `check` and goes red if someone forgot. The first-time setup is: apply plugin → `./gradlew apiDump` → commit baselines.

go deeper

for a junior

Knows you apply a plugin and CI runs check; may not know the exclusion options.

for a middle

Sets up the baseline, relies on check, and configures apiValidation excludes including marker-based ones.

for a senior

Chooses nonPublicMarkers for experimental carve-outs deliberately and manages multi-module aggregate tasks.

for a principal

Designs the exclusion policy and CI gating so the stable contract stays minimal and experimental APIs evolve without breaking the stability promise.

## Applying the plugin ```kotlin // root build.gradle.kts plugins { kotlin("jvm") version "2.1.0" id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.17.0" } ``` Applying it in the **root** project handles all subprojects by default. On first setup, generate the baselines once: ```bash ./gradlew apiDump # creates api/<module>.api files git add api && git commit -m "Add API baselines" ``` ## CI enforcement — nothing extra needed The plugin registers `apiCheck` as a dependency of the standard `check` lifecycle task. So an existing CI pipeline that runs: ```bash ./gradlew check ``` already enforces BCV. You can also call it directly (`./gradlew apiCheck`, or `apiCheckAll` for the whole multi-module tree). When someone changes the public API without running `apiDump`, `check` goes red with the offending diff. ## Shaping the surface with `apiValidation { }` ```kotlin apiValidation { // Exclude an entire implementation package from the contract ignoredPackages.add("com.acme.lib.internal") // Exclude one generated class ignoredClasses.add("com.acme.lib.BuildConfig") // Treat anything annotated with a marker as non-public nonPublicMarkers.add("com.acme.lib.InternalApi") // Opt whole modules out ignoredProjects.add("samples") // Hard kill-switch (rarely wanted) validationDisabled = false } ``` - **`ignoredPackages`** — coarse exclusion of a package prefix; good for an `internal` impl package you never want consumers to see in the contract. - **`ignoredClasses`** — surgical exclusion of a single generated/synthetic class. - **`nonPublicMarkers`** — the cleanest option for an *experimental* surface: annotate experimental declarations with your own `@InternalApi`/`@ExperimentalAcme` and list the marker; they are then omitted from the stable dump while staying `public` for consumers who opt in. - **`ignoredProjects`** — exclude sample/test modules that should not have a tracked ABI. ## Practical workflow recap 1. Apply plugin in root, run `apiDump`, commit `api/`. 2. CI runs `./gradlew check` → enforces `apiCheck`. 3. Intentional API change → `apiDump` → review/commit `.api`. 4. Experimental code → annotate + `nonPublicMarkers` so it never enters the contract. This keeps the stable contract small and deliberate while letting experimental APIs evolve freely.

  • What's the difference between ignoredPackages and nonPublicMarkers for hiding experimental API?
    ignoredPackages excludes by package path; nonPublicMarkers excludes by annotation. The marker approach lets experimental declarations live anywhere yet stay out of the stable contract — usually the cleaner choice.
  • Do you need a separate CI step to run apiCheck?
    No — the plugin wires apiCheck into `check`, so any pipeline running `./gradlew check` already enforces it; running `apiCheck`/`apiCheckAll` directly also works.

saying these in an interview costs you the question

  • Adding a bespoke CI task instead of relying on `check`
  • Not committing the generated api/ baselines
  • Hardcoding exclusions per-class when a marker annotation is cleaner
  • Disabling validation globally to silence a single failure
  • Forgetting apiDump on first setup so apiCheck has no baseline

context