At runtime, how does the Kotlin compiler represent a @JvmInline value class, and what does the underlying property's type have to be?
answer
- underlying type substituted in bytecode
- wrapped property: any type incl. nested value class
- primitives stay primitives, no autoboxing
- function names get mangled (hash suffix)
- @JvmName to fix Java interop
basics
~10 sWhere possible the compiler throws away the wrapper and uses the wrapped value directly, so there's no extra object. The single property can hold almost any type, including another value class.
solid answer
~50 sFor a `@JvmInline value class W(val v: T)`, the compiler emits the underlying type `T` in the bytecode wherever it safely can, so a `W` parameter or local becomes just a `T` with no allocation. The wrapped property can be of any type: primitives (`Int`, `Long`), reference types (`String`), generics, or even another value class (nesting). It cannot reference itself recursively, and it can't be a backing field that introduces extra state — only the one constructor property is the field. Compiled functions that take or return a value class get a **mangled name** (a hash suffix) so overloads that differ only by their value-class wrapper don't clash at the JVM level. This unboxed form is the whole point: a `value class Meters(val v: Double)` performs like a raw `Double` in tight loops, while still being a distinct compile-time type.
code
kotlin · 8 lines@JvmInline
value class Meters(val v: Double)
// Compiles to a method over `double` -- no Meters object allocated
fun add(a: Meters, b: Meters): Meters = Meters(a.v + b.v)
// JVM name is mangled, e.g. add-XXXX(double, double): double
// Use @JvmName("add") to expose a clean name to Java if neededgo deeper
Knows that the wrapper is replaced by the underlying value and that no object is normally allocated.
Can describe the bytecode substitution, that the property type is flexible, and that nesting is allowed.
Explains name mangling, the Java-interop @JvmName fix, and when unboxing applies vs. not.
Reasons about ABI stability, mangled-name impact on libraries, and interop trade-offs of exposing value-class APIs.
## The unboxed representation Given: ```kotlin @JvmInline value class Meters(val v: Double) ``` Where the compiler can prove it's safe, it replaces every `Meters` with a plain `double` in the generated bytecode. So: ```kotlin fun travel(d: Meters): Meters = Meters(d.v * 2) ``` compiles roughly to a JVM method taking a `double` and returning a `double` — **no `Meters` object is created**. That's why a value class is described as *zero-overhead*: in the common path it allocates nothing and performs identically to the raw value. ## What type can the property be? The single `val` can wrap essentially any type: - A primitive-backed type: `Int`, `Long`, `Double`, `Boolean`. - A reference type: `String`, a domain object, a collection. - A generic type parameter: `value class Box<T>(val v: T)`. - **Another value class** (nesting is allowed): `value class Wrapper(val id: UserId)`. What is **not** allowed: the property cannot make the class recursive in a way that has no finite representation, and the class can't have a second stateful property. ## Name mangling Because two functions like `fun f(x: Meters)` and `fun f(x: Double)` would both look like `f(double)` after unboxing, the Kotlin compiler **mangles** the JVM name of functions that use value classes — it appends a stable hash suffix (e.g. `travel-abc123`). This: - Prevents accidental signature clashes after unboxing. - Makes such functions awkward to call directly from Java (you may need `@JvmName` to expose a clean name). ## When it stays unboxed vs. boxes The value stays unboxed for ordinary typed usage. It **must box** (allocate the wrapper) when used as a nullable type, as a generic type argument, when assigned to a supertype like `Any`, or when the interface-typed view is needed — but those boxing rules are a separate topic. The key takeaway here: the *default* compiled form is the underlying value, and the wrapped property's type is flexible. ```kotlin @JvmInline value class UserId(val raw: Long) @JvmInline value class AdminId(val id: UserId) // nesting another value class is fine ```
- Why does the compiler mangle value-class function names?After unboxing, different value classes (and the raw type) collapse to the same JVM signature; mangling appends a hash to keep signatures distinct and avoid clashes.
- Can a value class wrap another value class?Yes, nesting is allowed, e.g. value class AdminId(val id: UserId).
saying these in an interview costs you the question
- Saying the wrapped property must be a primitive only
- Claiming the wrapper object is always allocated
- Not knowing function names are mangled
- Saying you can have a backing var or extra stored field
- Thinking value classes can be called cleanly from Java without @JvmName