Interview QuestionPractical QuestionFollow-up Questions

Value Classes in Kotlin

skydovesJaewoong Eum (skydoves)||8 min read

Value Classes in Kotlin

A value class is a special kind of class that wraps a single value and is optimized by the compiler to avoid unnecessary object allocation at runtime. While a value class provides type safety and semantic clarity at the source code level, the compiler inlines the wrapped value at call sites wherever possible, eliminating the overhead of a wrapper object. This makes value classes ideal for distinguishing between different usages of the same primitive type without paying a runtime cost. By the end of this lesson, you will be able to:

  • Explain how value classes are compiled and when the wrapper object is eliminated.
  • Identify the constraints that apply to value class declarations.
  • Describe the scenarios where boxing still occurs despite using a value class.
  • Apply value classes to enforce type safety in function parameters and domain models.

Declaration and Compilation

A value class is declared with the @JvmInline annotation and the value keyword. It must have exactly one read only property defined in the primary constructor.

@JvmInline
value class UserId(val id: Long)

@JvmInline
value class OrderId(val id: Long)

At the source level, UserId and OrderId are distinct types. A function accepting UserId will not compile if you pass an OrderId. At runtime, the compiler replaces usages of these classes with the underlying Long value wherever possible, avoiding object allocation entirely.

fun fetchUser(userId: UserId) {
    // At runtime, userId is just a Long
}

The compiler generates a mangled function name to prevent signature clashes when two functions differ only in their value class parameter types.

Constraints

Value classes have several restrictions that enable the compiler optimization:

  • Only one val property is allowed in the primary constructor.
  • They cannot have init blocks with logic that produces side effects.
  • They cannot extend other classes but can implement interfaces.
  • They cannot be used in identity based operations such as ===, because the wrapper object may not exist at runtime.

These constraints ensure that the compiler can safely replace the wrapper with the underlying value without changing program behavior.

When Boxing Occurs

Despite the inline optimization, boxing still happens in specific situations. When a value class is used as a nullable type, stored in a generic container, or passed to a function that accepts Any, the runtime must create an actual wrapper object because the underlying primitive cannot represent these cases directly.

This interview continues for subscribers

Subscribe to Dove Letter for full access to exclusive interviews about Android and Kotlin development.

Become a Sponsor