All Articles

Val, var, copy - Immutability in Kotlin

Changing tree colours
While change can be pretty, immutability has much value in coding!

The concept of immutability is a big one in Coding. Pretty much any language has options to make values immutable, as it gives developers a certainty that the object they are handling is indeed what they expect.

Beginners especially see this as some inconvenience - it’s so much easier to simply always change your object to what you need. And that may be true. However, it can be extremely hard to debug these changes, as you may struggle to find where exactly the unwanted or incorrect change has taken place.

You may think of immutability as the Save as button. You are always creating a new object, and can track all updates. This ensures a great predictability.

Val and var

val stands for value, and is the main keyword to ensure immutability. It is equivalent to the final keyword in Java.

Same as final, val only makes the reference immutable. It does not mean that the object cannot be changed in any place.

For example, the code below is absolutely valid:

    data class Person(var firstName: String)
    @Test
    fun `val`(){
        val p = Person("Gary")
        println(p)
        p.firstName = "Joe"
        println(p)
    }
Val-Var test
Val person is not that immutable

Of course, the code above only compiles because the firstName was not declared as val, but instead as var.

So, now you may argue that this has no advantage whatsoever over Java. And, technically, that’s true. However, I would say that in all my career, I have never seen any developer who has declared every single field in Java as final. Sure, there are some who are very rigorous about it. Others use Builders without subsequent Setters. These are all great approaches, however, everybody can forget to add a keyword.

In fact, public Person p compiles just as much as public final Person p. So forgetfulness may be the cause of making an object mutable.

In Kotlin, this is different. It needs to be clearly defined whether an Object (or field) is to be val or var. It’s not just an additional keyword. Therefore, if you see something like var p: Person, this indicates that someone has effectively decided that p is supposed to be mutable. This decision might have been taken poorly, but it was still a conscious decision.

This is why in Kotlin, you generally see larger declarations of classes, which are immutable all the way (and not simply upon declaration) as such:

data class AccountDto(
    val id: UUID? = null,
    @NotNull val name: String,
    @NotNull val balance: Double,
    @NotNull val currency: Currency,
    val description: String? = null,
    val addedOn: LocalDate? = null,
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    val snapshots: List<AccountSnapshotDto> = listOf()
)

Now, here, upon the first glance, any new developer immediately knows that when the object has been instantiated, it will never change. If there was any unexpected behavior based on some strange value that didn’t fit (e.g. an odd character in the name, such as @), then he’d know that this was received as such by the application, and the input was simply incorrect.

Copy

Okay, cool. So now we’ve established that the reference for an object, or the value of the field (which is always an object, as Kotlin does not contain the concept of primitives), held by var is always immutable. Now, what do we need to do when we want to be able to update something? Should we simply change the declaration to var?

For example, let’s say we have the following scenario for a Person:

    data class Person(
        val id: Int? = null,
        val firstName: String,
        val lastName: String,
        val birthDate: LocalDate
    )

    @Test
    fun `update_val`() {
        val jane = Person(
            id = 420,
            firstName = "Jane",
            lastName = "Doe",
            birthDate = LocalDate.now().minusYears(30)
        )

        // Jane gets married and wants to change her name - she's very traditional
        jane.lastName = "Hubby"
    }

In this case, code would not even compile. The reason is not that the compiler is such a feminist and believes Jane should keep her maiden name - it just doesn’t budge on the immutability of var.

So, what’s the solution here? Should Jane simply not get married?

Now, people may hold all type of philosophical beliefs about the institution of marriage, but code-wise, she should absolutely be able to. So, what can be done here?

Well, the solution is simple. As a developer, we can decide which fields should be allowed to be changed, and which fields should not be. In this case, we can say that only the lastName should be mutable. So, shall we make this var?

The answer is no! If we make this mutable, we again have the same problem, that we will never know where things went wrong if suddenly the name is neither Doe nor Hubby.

So, that was a very long prologue to the actual solution… The solution is to create an entirely new person, based on the new input, and then copy over all those values!

Here, of course, the developer should be careful on how the endpoint works. If the new input contains all the values, then simply overriding is the best bet. This is anyway a best practice, as this way, we additionally ensure the operation to be idempotent.

Here’s how the update would take place:

    data class Person(
        val id: Int? = null,
        val firstName: String,
        val lastName: String,
        val birthDate: LocalDate
    )

    @Test
    fun `update_val`() {
        val jane = Person(
            id = 420,
            firstName = "Jane",
            lastName = "Doe",
            birthDate = LocalDate.now().minusYears(30)
        )

        // Jane gets married and wants to change her name - she's very traditional
        val newJaneDto = Person(
            firstName = "Jane",
            lastName = "Hubby",
            birthDate = LocalDate.now().minusYears(30)
        )

        //bringing the DTO and the existing Person entity together
        val updatedJane = newJaneDto.copy(id = jane.id)
    }

This code compiles just fine. The developers may have also taken into account that not only should the lastName be able to change, but people can legally change their firstName also. The birthDate can simply not be changed biologically, but the user may have made an error upon entering it the first time. So making this updatable is definitely fair.

Note however how the id cannot be changed. This value is probably assigned by the system, and since we’re not creating a new user, no new id should be provided!

MutableList vs List

Similar to regular objects, we have the option to make lists mutable and immutable. Of course, the val and var discussion applies here in the same way. There is the additional Layer though, that we can define whether the List can receive more items or not.

List

A List is by default immutable in Kotlin. The following code would not compile:

    val list: List<String> = listOf()
    list.add("hello there")

In Java, this would actually also generate an error if we call .add() on List.of(). However, it would generate an error during runtime, and not during compilation. This is a big time saver and potentially even an avoided Production bug in Kotlin!

MutableList

Instead, Kotlin has multiple interfaces for List Collections. Next to List, there is also MutableList, which actually extends List. However, again, the intention is immediately made clear upon the instantiation of the Object - the developer has decided that this collection can receive new items or get them deleted.

    val list: MutableList<String> = arrayListOf()
    list.add("hello there")

This code compiles just fine.

So, should the developer be just as disciplined here and completely avoid MutableList. Well, it’s again up to the developer. On this one, I’m not as strict. When you have a large entity with a couple of OneToMany fields, copying over all these, ensuring that you are not creating duplicates, may become quite complex. So on this one, personally, I check the requirements case-by-case. Any coding decision is always a tradeoff, and here I may favor the convenience of having the option to use .add(), and sacrificing a tiny bit of immutability. On DTOs though, I always use the simple List.

Conclusion

So, is there a right way and a wrong way? I would argue that yes, there is a right way. Then again, some people may be even more strict, and entirely disallow MutableList also. Others may favor the convenience of simply having var everywhere and claim that they for sure know what they’re doing. Be that as it may, I think I have made my position on this quite clear, and given you the tools to make just about anything truly immutable!