skip to content

value class & @JvmInline

A value class annotated @JvmInline wraps one val property and, where possible, compiles down to that underlying value. It is how you get a distinct UserId type without a wrapper object on the heap.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What is a Kotlin value class, and why must it be annotated with @JvmInline on the JVM?

level: juniorimportance: must knowfreq 70%

answer

  1. value keyword + exactly one val
  2. @JvmInline required on JVM, else won't compile
  3. zero-overhead wrapper, no heap allocation
  4. old name: inline class (pre-1.5)
  5. kotlin.jvm package

basics

~20 s

A value class wraps one value (like a String or Int) in a new type without the cost of a real object. On the JVM you add @JvmInline so Kotlin can store just the wrapped value instead of a wrapper object.

solid answer

~40 s

A value class is declared as `value class X(val v: T)` and holds exactly one read-only property in its primary constructor. It gives you a distinct compile-time type (e.g. `UserId` vs `OrderId`) for type safety, while the compiler tries to represent it at runtime as the underlying value with no wrapper allocation. On the JVM the `value` keyword alone is not enough: you must also add `@JvmInline`, because the JVM has no native value-type support, so this annotation tells the Kotlin compiler to use the inline (unboxed) JVM representation. Without `@JvmInline`, `value class` does not compile for a JVM target. The single property must be a `val`, and the class can still have computed properties, functions, and implement interfaces.

code

kotlin · 8 lines
kotlin
@JvmInline
value class Email(val value: String)

fun send(to: Email) { /* ... */ }

val e = Email("[email protected]")  // no wrapper object allocated at runtime
send(e)
// send("[email protected]")  // compile error: String is not Email

go deeper

for a junior

Knows the basic syntax, that it wraps one val, and that @JvmInline is needed on the JVM.

for a middle

Explains the type-safety vs performance motivation and that without @JvmInline it doesn't compile on JVM.

for a senior

Connects it to the old inline class history, init-block validation, and when the unboxed representation actually applies.

for a principal

Frames it as a multiplatform abstraction over absent JVM value types and reasons about API design implications of zero-overhead domain types.

## What a value class is A **value class** is a class whose identity is defined entirely by the single value it wraps. You declare it with the `value` soft keyword and exactly one property in the primary constructor: ```kotlin @JvmInline value class UserId(val raw: String) ``` The goal is a **zero-overhead type wrapper**: at compile time you get a brand-new type (so `UserId` and `OrderId` are not interchangeable, preventing argument mix-ups), but at runtime the compiler tries to use the *underlying* value (`String`) directly, with **no wrapper object allocated** on the heap. ## Why @JvmInline is required `value class` is a multiplatform Kotlin concept. The **JVM has no native notion of value types**, so Kotlin needs to know *how* to represent the class on that platform. The `@JvmInline` annotation tells the compiler: "use the **inline** representation" — i.e. substitute the wrapped value in place of the wrapper wherever possible. - On the JVM, `value class` **without** `@JvmInline` does **not compile**. - The annotation is in package `kotlin.jvm`. - Historically this feature was called `inline class` (the `inline class X(...)` syntax, before Kotlin 1.5). The modern form is `@JvmInline value class`. ## Rules of the single property - Exactly **one** property, declared as `val` in the primary constructor. `var` is not allowed. - The class can have additional members: computed `val`s (backed by the underlying field, not new state), functions, and `init` blocks. - It can implement interfaces but cannot extend a class (it implicitly inherits from `Any`). ```kotlin @JvmInline value class Password(private val value: String) { init { require(value.length >= 8) { "too short" } } fun masked(): String = "*".repeat(value.length) } ``` ## What you gain - **Type safety**: distinct domain types instead of bare `String`/`Int`. - **Performance**: no heap allocation in the common case, so it behaves like the raw value. - **Validation**: the `init` block can enforce invariants once at construction.

  • Can a value class hold two properties?
    No. The primary constructor must have exactly one property. For multiple fields you need a regular or data class.
  • Must the single property be val or can it be var?
    It must be val. Value classes are immutable, so var is disallowed.

Like writing a label on an envelope's contents: the type system sees a labeled, distinct thing, but at runtime you're really just handling the raw contents directly.

saying these in an interview costs you the question

  • Saying value class can wrap multiple properties
  • Claiming @JvmInline is optional on the JVM
  • Confusing value class with data class
  • Saying the property can be a var
  • Thinking it always allocates a wrapper object like a normal class

context

open as a page

At runtime, how does the Kotlin compiler represent a @JvmInline value class, and what does the underlying property's type have to be?

level: middleimportance: must knowfreq 55%

basics

~10 s

Where 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.

open as a page

When would you choose a @JvmInline value class over a single-field data class? What members can a value class still have?

level: middleimportance: should knowfreq 50%

basics

~20 s

Use a value class when you want a typed wrapper around one value with no runtime cost. A single-field data class always creates a real object. Value classes can still have functions, computed properties, init checks, and implement interfaces.

open as a page

How are equals/hashCode generated for a @JvmInline value class, and what are the implications of a private wrapped val plus init-block validation?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Equality and hashCode come from the one wrapped value, so two wrappers are equal when their values are equal. You can make the value private and validate it in an init block so invalid instances can never exist.

open as a page

What are the hard constraints on a @JvmInline value class declaration, and what interop/design consequences follow from name mangling?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

A value class must have exactly one immutable property, can't store extra state, can't extend a class, and can't be inner. Because Kotlin renames its functions, calling them from Java is awkward and you usually need @JvmName.

open as a page