skip to content

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%

answer

  1. Green check = no signature drift, not a safe release
  2. Blind to behavior, inline bodies, const values
  3. Binary vs source compat are independent
  4. Excluded/out-of-band: reflection, serialized formats, SPI
  5. Layer deprecation cycles + behavioral tests + SemVer

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.

solid answer

~40 s

BCV guarantees only that the **signature-level public ABI** matches the committed `.api`. Its blind spots: (1) **behavioral/semantic** changes — same signature, different runtime behavior (throws now, returns null, different ordering); (2) **inline function bodies** and **`const val` values**, which are baked into consumers — changing them is binary-breaking with an identical signature; (3) **source-only breaks** that aren't binary breaks but still break recompilation (e.g. adding an overload that creates ambiguity, tightening a generic bound); (4) anything excluded via `apiValidation` (`ignoredPackages`, `nonPublicMarkers`) or living in a different artifact (resources, serialized formats, reflection-accessed names); (5) cross-platform/Native/JS ABI nuances beyond the JVM dump's scope. So 'apiCheck is green' means *no accidental signature drift* — necessary, not sufficient. A safe release still needs human review of behavior, deprecation cycles, and a real SemVer decision.

go deeper

for a junior

Recognizes the check only looks at the recorded API, not behavior.

for a middle

Names a few blind spots (behavior, excluded packages) and that source vs binary differ.

for a senior

Systematically enumerates the blind spots with concrete examples and knows the exclusion DSL widens them.

for a principal

Frames compatibility as a whole-contract property, positions BCV as one necessary guard, and designs the surrounding release governance (deprecation, behavioral tests, SemVer, multiplatform).

## What 'green apiCheck' actually proves It proves exactly one thing: **the current public binary signatures equal the committed `.api`.** That is a narrow, syntactic guarantee. It does not certify a backward-compatible *release*. ## Blind spots ### 1. Behavioral / semantic compatibility The signature `fun parse(s: String): Result` is unchanged, but the implementation now throws on empty input or returns results in a different order. Consumers break at runtime; the `.api` is byte-identical. BCV sees *shapes*, not *contracts*. ### 2. Inlined / compile-time-baked elements - `inline` function **bodies** are copied into call sites; changing a body re-breaks only after consumers recompile, yet old compiled callers keep the *stale* body — a subtle skew BCV cannot represent. - `const val` **values** are inlined into consumers; changing `const val MAX = 10` to `20` leaves the signature identical but ships a different value to anyone who recompiles vs. anyone who didn't. ### 3. Source-compatible-but-not vs binary-compatible-but-not The two compatibilities are independent: - Adding an overload can be binary-safe but cause **source** ambiguity, breaking downstream recompilation. - Reordering default-parameter-only changes, widening exceptions, or relaxing nullability can be binary-safe yet semantically risky. BCV tracks binary signatures, so source-only regressions can pass. ### 4. Excluded / out-of-band surface Anything matched by `ignoredPackages`, `ignoredClasses`, or `nonPublicMarkers` is deliberately outside the contract. So are non-ABI assets: serialized/persisted formats, JSON schemas, reflection-accessed member names, SPI/`META-INF/services` files, and resources. Renaming a class used only via reflection passes apiCheck but breaks consumers. ### 5. Platform scope The JVM `.api` dump is the common case; KLib/Native/JS have their own ABI considerations and tooling maturity differs. A green JVM check says nothing about other targets unless those are also validated. ## What a *safe* release process layers on top ```text apiCheck (no accidental signature drift) <- BCV + explicit @Deprecated cycles before removal + behavioral test suite / compatibility tests + conscious SemVer decision on the reviewed .api diff + checks on serialized formats & reflection contracts + per-target ABI validation if multiplatform ``` ## The principled framing BCV converts *accidental* signature breakage into a build failure — high value, low cost. But compatibility is a **whole-contract** property: behavior, inlined values, source ergonomics, and out-of-band formats all count. Treating a green check as proof of a safe release is a category error; it is one necessary guard among several.

  • Give a concrete change that is binary-compatible (apiCheck green) yet breaks consumers.
    Changing a public method to throw on input it used to accept, or changing a public `const val`'s value — both keep the signature identical but alter runtime behavior/values for consumers.
  • How would you cover the gaps BCV leaves?
    Enforced @Deprecated cycles before removal, behavioral/compatibility test suites, explicit SemVer review of the .api diff, and separate checks for serialized formats and reflection-accessed names.

It's a spell-checker for your API surface: it catches typos in the signatures, not whether the sentence still means what readers expect.

saying these in an interview costs you the question

  • Treating a green apiCheck as proof the release is backward compatible
  • Conflating binary and source compatibility as the same thing
  • Ignoring inline-body/const-value and behavioral changes
  • Forgetting reflection/serialized formats live outside the ABI
  • No mention of deprecation cycles or SemVer in the release process

context