Value Classes

Type Safety Without Overhead

A @JvmInline value class wraps a single value. At compile time it is a separate type (you cannot pass a UserId where an OrderId is expected), but at runtime it is usually just the underlying value, with no extra object.

Example

@JvmInline
value class UserId(val raw: Long)

@JvmInline
value class OrderId(val raw: Long)

@JvmInline
value class Rupees(val paise: Long) {
    init { require(paise >= 0) { "amount cannot be negative" } }
    operator fun plus(other: Rupees) = Rupees(paise + other.paise)
    override fun toString() = "Rs %d.%02d".format(paise / 100, paise % 100)
}

fun cancelOrder(user: UserId, order: OrderId) = "User ${user.raw} cancelled order ${order.raw}"

fun main() {
    println(cancelOrder(UserId(7), OrderId(1001)))
    // cancelOrder(OrderId(1001), UserId(7))   // compile error: types are swapped
    val total = Rupees(49_900) + Rupees(4_950)
    println("Total: $total")
    println(runCatching { Rupees(-1) }.exceptionOrNull()?.message)
}

Output

User 7 cancelled order 1001
Total: Rs 548.50
amount cannot be negative

Tip: Use value classes for IDs, money, emails and units. They catch "wrong argument order" bugs at compile time for free.