From a library/API-design perspective, what are the trade-offs of exposing a `typealias` versus a `@JvmInline value class` in a public API, especially regarding binary compatibility and Java interop?
answer
- typealias = transparent, callers depend on underlying type, no ABI boundary
- value class params → mangled JVM names; @JvmName for Java
- Boxing at nullable/generic/interface boundaries
- Underlying-type change is breaking; value class is final
- Frameworks may need custom serializers/converters
basics
~20 sA typealias is just a name that disappears, so callers really depend on the underlying type; you can't truly change it later without breaking them. A value class is a real, safer type but adds wrapping, possible boxing, and trickier Java interop because Kotlin renames its methods.
solid answer
~50 sA public `typealias` is **transparent**: at the binary level callers depend on the **aliased type**, not the alias, so the alias gives you no real abstraction boundary — you can't swap the underlying type without source/binary breakage, and the alias adds zero safety. It is great for naming function/generic types but is essentially a documentation aid. A public `@JvmInline value class` gives **type safety** and zero-cost wrapping, but introduces real API-evolution and interop concerns: (1) **JVM name mangling** — functions with value-class params get hashed names, so Java callers can't call them naturally without `@JvmName`; (2) **boxing** at nullable/generic/interface boundaries can surprise consumers; (3) changing the underlying type or adding members has binary-compatibility implications, and value classes can't participate in class inheritance; (4) serialization frameworks may need custom handling. The principled choice: use a `value class` when enforcing domain identity across an API surface is worth the interop friction; use a `typealias` only for readability where you accept full transparency.
go deeper
Knows typealias is for naming and value class adds safety, without the ABI nuance.
Notes value classes add wrapping cost and that aliases don't hide the underlying type.
Explains mangling, boxing at boundaries, and serialization concerns when exposing value classes publicly.
Builds a full decision framework weighing ABI/binary compatibility, Java interop, evolution constraints, and consumer ergonomics.
## typealias in a public API A `typealias` is a **compile-time alias**; it is **not** part of the binary ABI as a distinct entity. Consumers compile against the **underlying type**. ```kotlin typealias ConnectionId = Long // callers really see Long everywhere ``` Consequences: - **No abstraction boundary**: you cannot later redefine `ConnectionId` to a different underlying type without breaking every caller (it changes their effective signatures). The alias does not *protect* the underlying type from change. - **No safety**: callers can pass any `Long`. - **Java interop**: Java sees only `long`/`Long`; the alias is invisible. - **Good for**: shortening verbose function/generic types in your public surface for readability (`typealias Handler = (Event) -> Unit`). ## @JvmInline value class in a public API Gives a genuine, type-safe, mostly zero-cost wrapper — but has real API-design weight: ### 1. JVM name mangling A function taking a value-class parameter is compiled with a **mangled** JVM name (a hash suffix) so signatures stay unique after the param erases to its underlying type and so Java cannot call it in a type-unsafe way. ```kotlin @JvmInline value class Token(val raw: String) fun authenticate(t: Token) {} // JVM name like authenticate-<hash> ``` Java callers can't invoke `authenticate` naturally. Add `@JvmName("authenticate")` (and possibly a Java-friendly overload) to expose a stable name. ### 2. Boxing at boundaries Returning `Token?`, exposing `List<Token>`, or implementing an interface forces **boxing**, so consumers in hot paths may pay allocation you didn't intend. Document where unboxed representation holds. ### 3. Binary compatibility & evolution - Changing the **underlying type** is a breaking change (signatures effectively change). - Value classes are **final** (no inheritance), so you can't evolve them into a hierarchy. - Adding members is generally source-compatible but mind the mangled signatures. - Reflection and frameworks (serialization, ORMs, DI) may need **custom converters/serializers** because the runtime form is the underlying value. ### 4. Equality semantics Structural `equals`/`hashCode` from the single value — predictable for API consumers, but boxing can interact with identity-based assumptions. ## Decision framework | Concern | typealias | value class | |---|---|---| | Type safety on the surface | none | enforced | | Hides/locks underlying type | no | yes (real type) | | Java callable cleanly | yes | needs @JvmName for mangled fns | | Boxing surprises | none | possible (nullable/generic/iface) | | Serialization | trivial | may need custom support | | Inheritance/evolution | n/a | final, constrained | **Principle**: reach for a `value class` when the public surface genuinely benefits from a distinct, validated domain type and you accept the interop/serialization cost; reach for a `typealias` only to *name* a type for readability, knowing it is fully transparent and adds nothing at the ABI level.
- Why can't a public typealias be used as a stable abstraction to later change the underlying type?Because it is erased — consumers compile against the underlying type itself, so any change to it changes their effective signatures and breaks them. The alias is not a real boundary.
- How do you make a function with a value-class parameter cleanly callable from Java?Annotate it with `@JvmName` to override the mangled name, and/or provide a Java-friendly overload that takes the underlying type and wraps internally.
saying these in an interview costs you the question
- Treating a public typealias as a real abstraction boundary that hides the underlying type
- Unaware of value-class JVM name mangling and its Java-interop cost
- Ignoring boxing surprises for API consumers
- Forgetting serialization frameworks may need custom handling
- Assuming a value class can be evolved via inheritance