skip to content

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%

answer

  1. Records JVM descriptors of public/protected surface
  2. internal is mangled & excluded; @PublishedApi is INCLUDED
  3. inline bodies + const values: signature same, still binary-breaking
  4. Bridge/$default synthetics appear
  5. apiValidation { } : ignoredPackages, nonPublicMarkers

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.

solid answer

~40 s

The `.api` records the **binary** public surface as JVM descriptors: `public`/`protected` classes plus their `public`/`protected` members, with mangled JVM signatures. `private` and `internal` members are excluded from the public ABI — though `internal` ones are mangled, not absent from the bytecode. Two Kotlin-specific subtleties matter: (1) `internal` members annotated `@PublishedApi` *do* appear, because `public inline` functions can call them, so they are part of the binary surface consumers depend on; (2) `inline` functions themselves are listed — their bodies are copied into call sites, so changing one is binary-breaking even though the symbol looks unchanged. You can also tune the surface: exclude packages/classes/annotations via the `apiValidation { }` extension (e.g. `ignoredPackages`, `nonPublicMarkers`), and BCV honors visibility but reports the *compiled* shape, including synthetic bridge methods and property accessor (`getX`/`setX`) signatures.

go deeper

for a junior

Knows public stuff is in the dump and private is not.

for a middle

Adds that internal is excluded and that .api uses JVM signatures, not Kotlin source.

for a senior

Explains @PublishedApi inclusion, inline/const blind spots, and tunes the surface via apiValidation { }.

for a principal

Reasons about the full binary-compatibility model — synthetic bridges, mangling, experimental-marker carve-outs — and where BCV's guarantees end so the team layers manual review.

## What the dump captures The `.api` is a textual list of **JVM-level signatures** for everything reachable through the public binary surface: - `public` and `protected` classes/interfaces/objects (with supertypes and modifiers). - Their `public`/`protected` members: functions (as JVM method descriptors), and properties (as their generated `getX`/`setX` accessors plus any backing field that is part of the surface). - Synthetic artifacts the compiler emits that are observable in the ABI, e.g. **bridge methods** from generics/covariance and `$default` overloads for default parameters. It is the *compiled* shape, not the source shape — which is why it catches things source-level review misses. ## What is excluded - `private` members. - `internal` members — they are **name-mangled** in bytecode (e.g. `parse$mylib`) and not considered part of the stable public ABI, so they are omitted by default. - Members of excluded packages/classes (see tuning below). ## Kotlin-specific subtleties ### `@PublishedApi internal` A `public inline` function whose body is inlined into consumer code may call an `internal` helper. For that to link, the helper must be visible in the bytecode, so it is marked `@PublishedApi internal`, which makes it effectively public *binary*. **BCV includes `@PublishedApi` members** in the dump — they are part of what consumers depend on. ```kotlin @PublishedApi internal fun engine(): Engine = Engine() public inline fun run(block: () -> Unit) { engine().start() // inlined into the caller -> engine() must be in the ABI block() } ``` ### `inline` functions `inline` function bodies are copied into call sites at compile time. Changing the body (not just the signature) can be **binary-breaking** for already-compiled callers, even though the listed signature is unchanged. The signature appears in the `.api`; reviewers must additionally reason about body changes — BCV cannot diff inlined bodies. ### `const val` / compile-time constants Values of `const val` are inlined into consumers, so changing a constant's value is binary-breaking even though the signature is identical. ## Tuning the surface The `apiValidation { }` DSL controls what is dumped: ```kotlin apiValidation { ignoredPackages.add("com.acme.internal") ignoredClasses.add("com.acme.GeneratedConfig") // treat anything annotated with these as non-public nonPublicMarkers.add("com.acme.InternalApi") validationDisabled = false } ``` `nonPublicMarkers` lets you carve out an explicitly-marked experimental/internal surface so it is excluded from the stability contract. ## Takeaway The dump mirrors the *binary* contract, including compiler-synthesized and `@PublishedApi` members — but inline/const *value/body* changes are binary-breaking in ways the signature line alone won't reveal.

  • Why does an @PublishedApi internal member show up in the .api dump?
    Because public inline functions inline their bodies into consumers and may call that member, so it must be visible in the bytecode — it is effectively part of the binary public surface.
  • Can BCV catch a breaking change to a const val or an inline function body?
    No. The signature is unchanged, so the .api line is identical. Those value/body changes are binary-breaking but invisible to the dump; they need human awareness.

saying these in an interview costs you the question

  • Claiming internal members are fully absent from bytecode (they are mangled, not gone)
  • Thinking @PublishedApi members are excluded from the dump
  • Asserting BCV detects inline-body or const-value breaking changes
  • Not knowing the apiValidation DSL exists for tuning the surface
  • Confusing source visibility with binary visibility

context