How do I protect a List inside a Java record?

A record is a convenient way to represent data, but a record containing a mutable list can still change unexpectedly. In this tutorial, you will protect a reading list from changes made through other references.

Records became a final language feature in Java 16. This tutorial uses Java 17 as its baseline and requires no preview or incubator flags. See Java’s language changes by release.

1. Understand the problem

Consider this record:

record ReadingList(String title, List<String> books) {
}

Java generates its constructor and accessors. However, the generated constructor stores the supplied list reference. A caller holding that list can change its contents afterward.

A final field prevents reassignment of the reference; it does not freeze the referenced object. Java’s record documentation explains the fields and methods generated for records.

2. Protect the list during construction

Use a compact constructor to replace the incoming list with an unmodifiable snapshot:

ReadingList {
    books = List.copyOf(books);
}

Assign to the constructor parameter, books. Java assigns the resulting value to the component field after the constructor body completes.

List.copyOf() preserves element order and prevents later changes to the original list from affecting the result. It rejects null collections and null elements. See the Java 17 List API.

3. Create the complete example

Save this code as RecordListExample.java:

import java.util.ArrayList;
import java.util.List;

public class RecordListExample {
    record ReadingList(String title, List<String> books) {
        ReadingList {
            if (title == null || title.isBlank()) {
                throw new IllegalArgumentException(
                        "Title must not be blank");
            }
            books = List.copyOf(books);
        }
    }

    public static void main(String[] args) {
        List<String> original =
                new ArrayList<>(List.of("Java Basics"));

        ReadingList readingList =
                new ReadingList("Weekend", original);

        original.add("Advanced Java");

        System.out.println("Original: " + original);
        System.out.println("Record: " + readingList.books());

        try {
            readingList.books().add("Another Book");
        } catch (UnsupportedOperationException exception) {
            System.out.println("Record list cannot be modified");
        }
    }
}

The example checks two paths: modifying the original list and attempting a modification through the record’s accessor.

4. Compile and run

Open a terminal in the directory containing the file:

javac --release 17 RecordListExample.java
java RecordListExample

Output:

Original: [Java Basics, Advanced Java]
Record: [Java Basics]
Record list cannot be modified

The original list accepts another book. The record keeps its initial contents, and its returned list rejects additions.

5. Know the limit: elements are not copied

This approach protects the list structure. It does not create separate copies of its elements.

Here, the elements are strings, which are immutable. If the list contained mutable Book objects, callers could still change those objects. Use immutable element types or copy mutable elements when your data model requires deeper protection.

Best practices

  • Copy incoming collections at construction boundaries.
  • Document your null policy; this example rejects null lists and null elements.
  • Prefer immutable element types for value objects.
  • Avoid unnecessary copying in the accessor when the stored list is already protected.
  • Treat the caught exception as a demonstration; normal application code should avoid attempting unsupported modifications.

Summary

A record does not automatically make a referenced list immutable. Using List.copyOf() in its compact constructor protects the list against external structural changes while keeping the record concise.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.