SyntaxStudy
Sign Up
Kotlin Value Classes and Inline Classes
Kotlin Beginner 1 min read

Value Classes and Inline Classes

Value classes (formerly inline classes) wrap a single value in a type-safe wrapper without runtime overhead. The compiler replaces the wrapper class with the underlying type in most situations, giving you the type safety of a dedicated class at the cost of a primitive. This is invaluable for domain modelling: wrapping a raw Int in a UserId value class means you cannot accidentally pass a ProductId where a UserId is expected, even though both are integers at runtime. The compiler enforces the distinction. Value classes must have exactly one val property in the primary constructor and may implement interfaces. They cannot extend classes. The @JvmInline annotation is required on the JVM target.
Example
@JvmInline
value class UserId(val value: Int)

@JvmInline
value class Email(val value: String) {
    init {
        require(value.contains('@')) { "Invalid email: $value" }
    }

    fun domain(): String = value.substringAfter('@')
}

@JvmInline
value class Meters(val value: Double) {
    operator fun plus(other: Meters) = Meters(value + other.value)
    operator fun times(factor: Double) = Meters(value * factor)
    override fun toString() = "${value}m"
}

data class UserRecord(val id: UserId, val email: Email)

fun findUser(id: UserId): UserRecord? {
    return if (id.value == 1)
        UserRecord(id, Email("alice@example.com"))
    else null
}

fun main() {
    val uid = UserId(1)
    val record = findUser(uid)
    println(record)

    // Type safety — cannot pass Email where UserId expected
    val email = Email("bob@example.com")
    println("Domain: ${email.domain()}")

    // Arithmetic with value class
    val a = Meters(3.0)
    val b = Meters(4.5)
    println(a + b)         // 7.5m
    println(a * 2.0)       // 6.0m

    // At runtime, UserId is just an Int — no boxing overhead
    val ids = listOf(UserId(1), UserId(2), UserId(3))
    ids.forEach { println("User ${it.value}") }
}