# Records in java :  the illusion of immutability

Java records, as I mentioned in my last [article](https://loukmanemaada.hashnode.dev/records-an-elegant-way-to-transfer-data-in-java), offer a new way to define classes that hold immutable data. Records make it easier to create data carriers with fixed states at creation. However, this immutability can be misleading when records have fields with mutable objects.

While you can't reassign the record's fields, the objects they reference, like mutable collections, can still be modified. This creates an illusion of immutability—where the record's structure looks immutable, but its contents <mark>can still change</mark>, going against the main idea of immutability that records aim to promote.

In this article, we'll look at why Java records might not be as immutable as they seem, especially with mutable collections, and discuss ways to handle this issue.

## **Understanding Java Records: A Quick Recap**

At its core, a record is a special kind of class in Java:

```java
public record User(String name) {}
```

This `User` record automatically generates the following:

* A constructor to initialize the field `name`.
    
* Accessor method `name()`.
    
* `equals()`, `hashCode()`, and `toString()` methods.
    

You might think that since the fields are `final`, the object is fully immutable. However, immutability only applies to <mark> the references </mark> stored in those fields, not to the objects they point to.

## **The Problem: Mutable Objects**

Let’s extend our `User` example to include a `List<String>` to store phone numbers:

```java
import java.util.List;

public record User(String name, List<String> phoneNumbers) {}
```

On the surface, this seems immutable because we can’t reassign `phoneNumbers` to a different list. But the real problem lies in the fact that `List<String>` itself is mutable/immutable depending on the implementation we choose to use, for demonstration purpuses, we will be using the `ArrayList` (a mutable collection) implementation:

```java
public class Main {
    public static void main(String[] args) {
        List<String> phoneNumbers = new ArrayList<>();
        phoneNumbers.add("+33 613-56-91-91");
        
        var user = new User("John", phoneNumbers);
        System.out.println(person.phoneNumbers()); // [+33 613-56-91-91]

        // Mutating the list from outside the 
        user.phoneNumbers().add("+33 615-25-40-49");
        System.out.println(user.phoneNumbers()); // [+33 613-56-91-91, +33 615-25-40-49]
    }
}
```

The record guarantees that the reference to `phoneNumbers` is immutable, but the contents of the list can still be changed. This breaks the illusion of immutability, as the `User` object can be modified indirectly.

To illustrate the immutability of the reference, let’s take a look at this code :

```java
public static void main(String[] args) {
        List<String> phoneNumbers = new ArrayList<>();
        phoneNumbers.add("+33 613-56-91-91");

        var user = new User("John", phoneNumbers);

        // error: cannot assign a value to final variable phoneNumbers
        user.phoneNumbers = new ArrayList<>(user.phoneNumbers());
    }
```

### **Why Does This Happen?**

Java’s immutability for records only applies to fields themselves, not to the objects those fields reference. Since `List` is mutable, we can modify its contents even though the reference itself is final. This applies to <mark>any mutable Object</mark>.

The mutability issue arises because collections in Java, by default, do not enforce immutability. While you can't reassign a new list to `phoneNumbers` in the `User` record, you can modify the list itself.

## **Alternatives: Enforcing Immutability**

To preserve true immutability with mutable Collections inside a record, you have a few options:

### **1\. Defensive Copying**

One common approach is to make a defensive copy of the collection during record initialization. This ensures that even if the original collection is mutable, the copy stored in the record is isolated from external modifications. This can be achieved by adding the following code to the canonical constructor of the `User` record.

```java
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public record User(String name, List<String> phoneNumbers) {
    public User{
        // Defensive copy
        phoneNumbers = List.copyOf(phoneNumbers);
    }
}
```

In this example, `List.copyOf(addresses)` creates an unmodifiable copy of the input list. Any attempts to modify this copy will result in `UnsupportedOperationException`.

### **2\. Using Unmodifiable Collections**

If you’re dealing with a collection that should never be modified, you can use Java's unmodifiable collections:

```java
javaCopy codeimport java.util.List;
import java.util.Collections;

public record Person(String name, int age, List<String> addresses) {
    public Person {
        // Wrapping the list as unmodifiable
        addresses = Collections.unmodifiableList(new ArrayList<>(addresses));
    }
}
```

Here, `Collections.unmodifiableList()` returns a read-only view of the list. While you can still pass a mutable list into the record, the record itself will only expose an unmodifiable version, preventing external changes.

### **3\. Generic alternative :**

If you need to maintain mutability outside of the record—meaning that the objects returned by the getters are still mutable but cannot affect the record’s internal state—you can achieve this by making defensive copies both during the record's initialization and in the getter method. This approach ensures that any mutations performed on the returned object don't affect the record’s internal data. Let’s look at the updated `User` record code :

```java
public record User(String name, List<String> phoneNumbers) {
    
    // Defensive copy in the constructor
    public User {
        // Create a defensive copy to prevent external modifications
        addresses = new ArrayList<>(addresses);
    }

    // Defensive copy in the getter
    @Override
    public List<String> addresses() {
        // Return a mutable copy of the addresses
        return new ArrayList<>(addresses);
    }
}
```

Now let’s run our code :

```java
public class Main {
    public static void main(String[] args) {
        List<String> phoneNumbers = new ArrayList<>();
        phoneNumbers.add("+33 613-56-91-91");
        
        var user = new User("John", phoneNumbers);
        System.out.println(person.phoneNumbers()); // [+33 613-56-91-91]

        // Mutating the list from outside the record
        user.phoneNumbers().add("+33 615-25-40-49");
        System.out.println(user.phoneNumbers()); // [+33 613-56-91-91]
    }
}
```

## **Conclusion**

While Java records are designed to simplify the creation of immutable data classes, the immutability they promise can be misleading when dealing with mutable objects. Since objects like `ArrayList` remain mutable, they can undermine the immutability of records.

To address this, you can adopt several strategies:

* Defensive copying.
    
* Using unmodifiable collections provided by Java’s `Collections` utility class (Collection specific sollution).
    
* Returning a copy of the object.
    

By incorporating these approaches, you can ensure that your Java records live up to the promise of immutability, even when mutable Objects are involved.
