ProgrammingKotlin Developer

How is the 'inline' keyword implemented concerning classes (value class/inline class) in Kotlin? What limitations exist, how do such classes work at the bytecode level, when and why should they be used? Provide an example and explain typical complexities.

Pass interviews with Hintsage AI assistant

Answer

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:

  • There can only be one property in the primary constructor.
  • Storing references for referential equality is not allowed (=== does not work as usual).
  • Value classes cannot be a subclass, nor can they have state other than their value.
  • Not all generic types and platform APIs can work with inline/value classes without boxing.
  • Value classes cannot have an init block, fields other than value, and only minimal functions.

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:

  • Ensuring type safety for identifiers, special values.
  • Improving performance when dealing with millions of such wrappers (no objects are created).

Trick question

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("@") }

Examples of real errors due to lack of knowledge on the topic


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.