What exactly ends up in a .api dump, and what Kotlin constructs are excluded or need special handling (e.g. internal, @PublishedApi, inline)?
answer
- Records JVM descriptors of public/protected surface
- internal is mangled & excluded; @PublishedApi is INCLUDED
- inline bodies + const values: signature same, still binary-breaking
- Bridge/$default synthetics appear
- apiValidation { } : ignoredPackages, nonPublicMarkers
basics
~20 sThe 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 sThe `.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
Knows public stuff is in the dump and private is not.
Adds that internal is excluded and that .api uses JVM signatures, not Kotlin source.
Explains @PublishedApi inclusion, inline/const blind spots, and tunes the surface via apiValidation { }.
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