Kotlin 2.5.0-Beta2 introduces experimental full value classes. Previously, inline value classes could hold one stored property. Full value classes allow several properties to describe a value together.
Consider the selected range in an Android video editor:
// Kotlin 2.5.0-Beta2 with -Xfull-value-classesvalue class PlaybackRange( val startMs: Long, val endMs: Long) { init { require(startMs >= 0 && startMs < endMs) }}
fun main() { val selected = PlaybackRange(10_000L, 30_000L) val restored = PlaybackRange(10_000L, 30_000L)
println(selected == restored) // true // selected === restored // Compilation error}Both ranges describe the same section of the video. One might come from the editor and the other from saved data.
A data class already provides equality based on its properties. The additional rule here is that a full value class prohibits direct identity comparisons on its type. This catches code that compares the ranges by object reference instead of their timestamps.
There’s a trade-off. The compiler generates equals(), hashCode() and toString(), but no automatic copy() or componentN() functions. Name-based destructuring is available instead. Kotlin documentation.
That doesn’t mean the entire screen state needs to become a value class:
data class PlayerUiState( val selection: PlaybackRange? = null, val isPlaying: Boolean = false)
val updated = PlayerUiState().copy( selection = PlaybackRange(10_000L, 30_000L))The range can be a full value class while the UI state keeps the convenience of copy(). Small value models like this are where I’d first evaluate the feature in an Android project.
For results that carry no data, there’s also value object:
sealed interface PlaybackResult
value class Ready(val range: PlaybackRange) : PlaybackResultvalue object NoSelection : PlaybackResultReady carries the range. NoSelection simply indicates that nothing is selected. A data object could support the same application flow. A value object adds the restriction against direct identity comparisons on its type, so existing data objects don’t need replacing. Value objects.
Performance requires some restraint. The initial implementation does not inline full value classes, and there’s no guaranteed reduction in allocations or GC work. Immutability is shallow too: a mutable list stored inside the class remains mutable. Runtime representation in KEEP-0454.
Automatic Compose recomposition improvements aren’t promised either. The KEEP discusses possible future compiler and state-tracking optimizations, with their benefits still to be investigated. Compose considerations.
To try the feature in a module using Kotlin 2.5.0-Beta2, add this to its build.gradle.kts:
kotlin { compilerOptions { freeCompilerArgs.add("-Xfull-value-classes") }}I’d consider it for small models such as ranges or coordinates. For a screen state with frequent copy() calls, I’d want a concrete benefit before migrating. Existing @JvmInline declarations can stay as they are; they remain valid. Inline migration.