Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A value class is a specialized Kotlin class designed to wrap a single underlying value, providing compile-time type safety while eliminating the runtime overhead of object allocation. During compilation, the Kotlin compiler aggressively inlines the wrapped value, replacing object instantiations with the underlying primitive or reference type in the generated bytecode.

Syntax

To declare a value class on the JVM backend, use the value modifier combined with the @JvmInline annotation.
@JvmInline
value class UserId(val id: String)

Structural Constraints

Value classes are subject to strict structural limitations to ensure the compiler can safely inline them:
  • Primary Constructor: Must contain exactly one read-only (val) property. Mutable properties (var) are not permitted.
  • Backing Fields: Cannot declare additional properties with backing fields. All secondary properties must be computed via custom getters.
  • Inheritance: Value classes are implicitly final. They cannot extend other classes, nor can they be extended.
  • Interfaces: They are permitted to implement interfaces.
  • Initialization: They can contain init blocks for validation logic.
  • Identity and Equality: Referential equality checks (=== and !==) are strictly prohibited by the compiler. Because value classes can be boxed and unboxed freely by the compiler, they do not possess a stable runtime identity. Only structural equality (== and !=) is permitted, which evaluates the underlying wrapped values.
interface Identifiable {
    fun getIdentifier(): String
}

@JvmInline
value class UserId(val id: String) : Identifiable {
    init {
        require(id.isNotBlank()) { "ID cannot be blank" }
    }

    // Computed property (no backing field allocated)
    val length: Int
        get() = id.length

    override fun getIdentifier(): String = id
}

Runtime Representation: Boxing and Unboxing

The compiler dynamically determines whether to use the unboxed (underlying) value or a boxed (instantiated object) representation based on the call site context.
  • Unboxed: When the value class is used as a concrete, statically typed variable or function parameter, the compiler uses the underlying type. No object allocation occurs.
  • Boxed: When the value class is used in a context that requires polymorphism—such as being assigned to an interface type or passed to a generic type parameter (<T>)—the compiler allocates a wrapper object on the heap.
  • Nullable Types: Boxing behavior for nullable value classes depends entirely on the underlying type. If the underlying type is a primitive (e.g., Int), using the value class as a nullable type forces boxing because JVM primitives cannot natively represent null. Conversely, if the underlying type is a reference type (e.g., String), the nullable value class is represented as a nullable reference (String?) under the hood, requiring no boxing.
@JvmInline value class UserId(val id: String)
@JvmInline value class Age(val value: Int)

// Unboxed: Compiled as processId(String)
fun processId(userId: UserId) {} 

// Boxed: Allocates a UserId object because of the generic context
fun <T> processGeneric(item: T) {} 

// Unboxed: String? can natively represent null, so UserId? becomes String?
fun processNullableId(userId: UserId?) {}

// Boxed: Int cannot natively represent null, so Age? allocates an Age object
fun processNullableAge(age: Age?) {}

Name Mangling

Because value classes are erased to their underlying types at the bytecode level, function overloading can lead to platform declaration clashes.
fun authenticate(id: String) {}
fun authenticate(id: UserId) {} // Bytecode erasure makes this authenticate(String)
To resolve this collision, the Kotlin compiler applies name mangling to functions accepting value classes. It appends a hash to the generated JVM method name (e.g., authenticate-<hash>(String)). This ensures distinct bytecode signatures for the JVM while maintaining the original, clean function names in the Kotlin source code.
Master Kotlin with Deep Grasping Methodology!Learn More