Editor's Note (January 2026): This article explores the innovative immutability features first proposed at KotlinConf 2025. While these features (like copy var and immutable value class) are officially on the JetBrains roadmap, they remain in the experimental design phase. The code examples below represent the proposed future syntax and are not yet available in stable Kotlin 2.x releases.

There is been a lot of exciting discussion recently about potential big changes for Kotlin regarding immutability. These are not fully confirmed features yet, but the core ideas of truly immutable data and value types being explored could be super useful for all kinds of Kotlin applications. This topic was first presented and discussed at KotlinConf 2025, all the talks from the conference are available on the official JetBrains Kotlin YouTube channel.

In this article, we will look at proposed features like immutable value class, @Immutable annotation, copy var and copy fun

The Current State of Immutability in Kotlin

Kotlin's data class with val properties has been a core part of writing cleaner, more predictable code for a long time. But working with data structures that are deeply nested and immutable can sometimes make things a bit complicated and wordy.

Let us think about a common situation: managing user profile data in an application.

// Data classes representing a user profile
data class Address(
    val street: String,
    val city: String,
    val zipCode: String
)

data class UserProfile(
    val name: String,
    val email: String,
    val address: Address
)

fun main() {
    // Initial user profile
    var userProfile = UserProfile(
        name = "Alice",
        email = "[email protected]",
        address = Address(
            street = "123 Main St",
            city = "Anytown",
            zipCode = "12345"
        )
    )

    println("Original Profile: $userProfile")

    // Scenario 1: Update the street of the address
    // This requires deep copying
    userProfile = userProfile.copy(
        address = userProfile.address.copy(
            street = "456 New St"
        )
    )
    println("After updating street: $userProfile")

    // Scenario 2: Update the user's email
    userProfile = userProfile.copy(
        email = "[email protected]"
    )
    println("After updating email: $userProfile")

    // Scenario 3: Simulate a function that modifies the profile
    fun updateProfileName(profile: UserProfile, newName: String): UserProfile {
        // Returns a new instance
        return profile.copy(name = newName)
    }

    userProfile = updateProfileName(userProfile, "Alice Smith")
    println("After function update: $userProfile")
}

While this code works, we often run into a few common problems:

  • Too Much Code for Nested Updates. Trying to change a property that is several layers deep quickly turns into a long chain of .copy().copy().copy()... calls, making code harder to read and manage.
  • Manual Copying. Every time we change something, we have to explicitly call copy() and then assign the result back to the variable (userProfile = ...). This is correct for immutability, but it can feel less natural than a direct assignment.
  • Shallow Immutability. Using val properties gives us shallow immutability. If Address or UserProfile had mutable collections inside (like mutableListOf(...)), the compiler would not stop you from changing them directly. This can lead to tricky bugs if you assume your data is fully immutable.

The Horizon: immutable value class & copy var

The Kotlin team is working on ways to fix these issues. They want to give us more ergonomic and truly deeply immutable data structures, enforced by a compiler. That is where the ideas of immutable value class, copy var and copy fun (new keywords) and an @Immutable annotation come in.

Imagine if updating nested immutable data looks just like updating a regular field, but all the benefits of immutability are handled automatically behind the scenes. Sounds pretty great. Let us look at the old example, but modified with new proposals:

// Mark Address as an immutable value class.
// 'copy var' on properties allow ergonomic "updates" 
// via copy semantics.
immutable value class Address(
    copy var street: String,
    copy var city: String,
    copy var zipCode: String
)

// UserProfile is also an immutable value class.
// Since 'Address' is also an immutable value class, 
// UserProfile is deeply immutable.
immutable value class UserProfile(
    copy var name: String,
    copy var email: String,
    copy var address: Address
)

// Example of a regular class that needs to be 
// deeply immutable to be used inside an immutable value class.
// We use @Immutable annotation as a contract to the compiler.
@Immutable
class AuditLog(val entries: List<String>) {
    fun addEntry(entry: String): AuditLog {
        return AuditLog(entries + entry)
    }
    override fun toString() = "AuditLog(entries=$entries)"
}

// UserProfile (immutable value class) now can contain 
// AuditLog (regular @Immutable class)
immutable value class UserProfileWithLog(
    copy var name: String,
    copy var email: String,
    copy var address: Address,
    // This property is 'val' because AuditLog 
    // updates itself via new instances
    val auditLog: AuditLog,
)

fun main() {
    // Initial user profile
    // 'copy var' at the declaration site enables 
    // mutable value semantics for this variable.
    copy var userProfile = UserProfile(
        name = "Alice",
        email = "[email protected]",
        address = Address(
            street = "123 Main St",
            city = "Anytown",
            zipCode = "12345"
        )
    )

    println("Original Profile: $userProfile")

    // Scenario 1: Update the street using 'copy var'
    // This looks like a direct mutation, 
    // but the compiler transparently handles the copying.
    userProfile.address.street = "456 New St"
    println("After updating street (copy var): $userProfile")

    // Scenario 2: Update the user's email using 'copy var'
    userProfile.email = "[email protected]"
    println("After updating email (copy var): $userProfile")

    // Scenario 3: Simulate a function that modifies
    // the profile using 'copy fun'
    // A 'copy fun' treats its 'this' receiver as a 'copy var'.
    copy fun UserProfile.normalizeEmail(): Unit {
        // Implicitly creates a new UserProfile
        email = email.toLowerCase()
    }

    userProfile.normalizeEmail()
    println("After normalizing email (copy fun): $userProfile")

    // Scenario 4: Update a property that is an @Immutable class
    copy var userProfileWithLog = UserProfileWithLog(
        name = "Bob",
        email = "[email protected]",
        address = Address("789 Oak Ave", "Somecity", "67890"),
        auditLog = AuditLog(listOf("Profile created"))
    )

    println("Original Profile with Log: $userProfileWithLog")

    // The 'auditLog' property itself is 'val' 
    // and its methods return new instances,
    // so we still assign a new instance to it, 
    // but the parent UserProfileWithLog will be copied implicitly.
    userProfileWithLog.auditLog = 
      userProfileWithLog.auditLog.addEntry("Address viewed")
    
    println("After adding audit entry: $userProfileWithLog")
}

Key Advantages

These forthcoming features are not just small tweaks; they offer big wins for developers:

  1. Easier to Read and Use
    • No more deep .copy() calls! The syntax userProfile.address.street = "..." is much more natural and short for "changing" immutable data, especially for complex structures.
    • copy fun lets you group many copy var updates into one clear function, making your code even more expressive.
  2. Compiler Makes Sure It is Deeply Immutable
    • immutable value class makes sure that instances of your data structures are truly immutable when your code is compiled. This is a huge help for preventing tricky bugs caused by accidentally changing data. immutable value class would prevent you from having MutableList or other mutable types directly as properties if deep immutability is intended, guiding you towards using immutable collections or @Immutable wrappers.
    • The @Immutable annotation acts like a promise to the compiler for regular classes. This lets them work well with deeply immutable value classes while still getting those important compiler checks.
  3. Performance Optimization Potential (Thanks to Project Valhalla!)
    • Project Valhalla is an ongoing OpenJDK effort to bring "value types" to the JVM. The goal is to bridge the gap between primitive types and objects, allowing "code like a class, work like an int." Instances of immutable value class, particularly when combined with these JVM advancements, could mean fewer new objects being created. Imagine your UserProfile and Address data being stored directly inside other structures or arrays, instead of as separate objects in memory. This means less overhead and less work for the garbage collector!
    • Even though copy var still means creating new copies, how these copies are made (with Valhalla value types behind them) can be much more efficient at the JVM level. This could mean highly optimized memory-level copies instead of creating full new objects.
  4. Safer for Concurrent Code
    • Immutable data is naturally safe to use with multiple threads. With better language support for immutability, it becomes much easier to design and understand systems that run tasks at the same time. 

In short, these new features offer a powerful mix of making it easier to work with immutable data and giving a boost to performance (with Project Valhalla's help). There is an open issue in the Jetbrains issue tracker if you want to follow the process. Link to the recording of a talk at KotlinConf 2025.