In Kotlin, inline classes (starting from Kotlin 1.5 — the term "value class") allow you to create wrappers over types with minimal overhead. At compile time, such classes are internally replaced with their value to avoid the overhead of object creation.
Limitations and features:
=== does not work as usual).Example:
@JvmInline value class UserId(val value: String) fun getUser(id: UserId) { println("Loading user with id: ${id.value}") } val id = UserId("XYZ") getUser(id) // Under the hood, works just with String!
When to use:
Can value classes inherit or be used in the hierarchy of interfaces/abstract classes?
Answer: No, value classes cannot inherit other classes (excluding interfaces), cannot be open for inheritance, do not allow init blocks, and other non-static fields. The only available option is to implement interfaces.
Example:
interface Validatable { fun isValid(): Boolean } @JvmInline value class Email(val raw: String) : Validatable { override fun isValid() = raw.contains("@") }
Story
An Android application suddenly increased startup time after adding value classes to Parcelable parameters: it turned out that incorrect @Parcelize with value class led to boxing/unboxing at every serialization stage, undermining the benefits of inline.
Story
A microservice started using value classes for UserId and ProductId for type safety, but in many places, generic functions required reflection, which did not work with the "wrapper". Unit tests unexpectedly began to fail, causing ClassCastException.
Story
Migrated code from Java started replacing internal domain classes with value classes for optimization, but their use as nullable fields led to unexpected null pointer exceptions, as a value class can be null only if the outer value is also null, which broke old invariants.