why java records aren’t quite immutable

A new type called a “record” was previewed in Java 14 and released in Java 16. One of the benefits is that it creates immutable objects. Kind of.

An immutable record

This is record is an immutable object. Since it is a record, it is automatically final and has no setters. All is good.

public record Book (String title, int numPages) { }

A mutable record

There there is this record. Do you see why it is mutable?

public record Book (String title, int numPages, List<String> chapters) { }

The problem is that records use shallow immutability. The caller can’t change the “chapters” object to a different reference. The caller can change the values in the “chapters” to their heart’s content. That means this object is still mutable.

Here’s an example showing that the code prints [1, 2, 3] and therefore changes the list of chapters.

List<String> chapters = new ArrayList<>();
chapters.add("1");

Book book = new Book("Breaking and entering", 289, chapters);

chapters.add("2");
book.chapters().add("3");
System.out.println(book.chapters());

Making the record actually be immutable

It’s pretty easy to make the Book record actually be immutable. In fact, it only requires three extra lines! Records have a compact constructor which takes care of the setting fields for you. However, you can choose to change that behavior. In this example, I rely on the default set of “title” and “numPages”. However, for “chapters”, I choose to make an immutable copy to prevent changing the list.

public record Book (String title, int numPages, List<String> chapters) {

    public Book {
        chapters = List.copyOf(chapters);
    }
}

Now the test program fails with an UnsupportedOperationException. Much better. A real immutable record.