Java arrays pose several challenges for records, and these added a number of constraints to the design. Arrays are mutable, and their equality semantics (inherited from Object) is by identity, not contents.

The basic problem with your example is that you wish that equals() on arrays meant content equality, not reference equality. The (default) semantics for equals() for records is based on equality of the components; in your example, the two Foo records containing distinct arrays are different, and the record is behaving correctly. The problem is you just wish the equality comparison were different.

That said, you can declare a record with the semantics you want, it just takes more work, and you may feel like is is too much work. Here's a record that does what you want:

record Foo(String[] ss) {
    Foo { ss = ss.clone(); }
    String[] ss() { return ss.clone(); }
    public boolean equals(Object o) { 
        return o instanceof Foo f && Arrays.equals(f.ss, ss);
    }
    public int hashCode() { return Objects.hash(Arrays.hashCode(ss)); }
}

What this does is a defensive copy on the way in (in the constructor) and on the way out (in the accessor), as well as adjusting the equality semantics to use the contents of the array. This supports the invariant, required in the superclass java.lang.Record, that "taking apart a record into its components, and reconstructing the components into a new record, yields an equal record."

You might well say "but that's too much work, I wanted to use records so I didn't have to type all that stuff." But, records are not primarily a syntactic tool (though they are syntactically more pleasant), they are a semantic tool: records are nominal tuples. Most of the time, the compact syntax also yields the desired semantics, but if you want different semantics, you have to do some extra work.

Answer from Brian Goetz on Stack Overflow
🌐
Baeldung
baeldung.com › home › java › core java › java record keyword
Java Record Keyword | Baeldung
November 5, 2025 - Records are immutable data classes that require only the type and name of fields. The equals, hashCode, and toString methods, as well as the private, final fields and public constructor, are generated by the Java compiler.
🌐
Oracle
docs.oracle.com › en › java › javase › 14 › docs › api › java.base › java › lang › Record.html
Record (Java SE 14 & JDK 14)
See Java Language Specification: 8.10 Record Types · Since: 14 · All MethodsInstance MethodsAbstract Methods · clone, finalize, getClass, notify, notifyAll, wait, wait, wait · protected Record() Constructor for record classes to call. public abstract boolean equals​(Object obj) Indicates whether some other object is "equal to" this one.
🌐
Oracle
docs.oracle.com › en › java › javase › 14 › language › records.html
Java Language Updates
JDK 14 introduces records, which are a new kind of type declaration. Like an enum, a record is a restricted form of a class. It’s ideal for "plain data carriers," classes that contain data not meant to be altered and only the most fundamental methods such as constructors and accessors.
🌐
DigitalOcean
digitalocean.com › community › tutorials › java-records-class
Java 14 Records Class | DigitalOcean
August 4, 2022 - Java 14 introduced a new way to create classes called Records.
🌐
DZone
dzone.com › coding › java › a first look at records in java 14
A First Look at Records in Java 14
January 7, 2020 - The upcoming release of Java will be version 14 scheduled to be in general availability in March 2020. Similar to the already released versions under the new 6-month release cycle, JDK 14 is expected to have several new features at both the language and JVM levels. If we look at the feature list, however, we notice quite a few language features that are highly anticipated by developers: records, switch expressions (which exist in JDK 13 but in preview mode), and pattern matching.
🌐
InfoQ
infoq.com › articles › java-14-feature-spotlight
Java 14 Feature Spotlight: Records - InfoQ
February 4, 2020 - Records aim to enhance the language's ability to model "plain data" aggregates with less ceremony. In this article Java Language Architect Brian Goetz takes a deep dive into the feature.
🌐
Spring Framework Guru
springframework.guru › home › exploring java 14 records: simplifying data classes in java
Exploring Java 14 Records: Simplifying Data Classes in Java
October 21, 2024 - Introduction Records are a new feature in Java 14. We can use them to avoid a lot of boilerplate code in standard DTO classes, save our time, and limit space for errors. In this tutorial…
🌐
Mkyong
mkyong.com › home › java › java 14 – record data class
Java 14 - Record data class - Mkyong.com
May 22, 2020 - Java is too verbose, if we want ... toString(). Finally, Java 14 introduced the record class to simplify the process by automatically creating all the tedious methods....
Find elsewhere
Top answer
1 of 3
46

Java arrays pose several challenges for records, and these added a number of constraints to the design. Arrays are mutable, and their equality semantics (inherited from Object) is by identity, not contents.

The basic problem with your example is that you wish that equals() on arrays meant content equality, not reference equality. The (default) semantics for equals() for records is based on equality of the components; in your example, the two Foo records containing distinct arrays are different, and the record is behaving correctly. The problem is you just wish the equality comparison were different.

That said, you can declare a record with the semantics you want, it just takes more work, and you may feel like is is too much work. Here's a record that does what you want:

record Foo(String[] ss) {
    Foo { ss = ss.clone(); }
    String[] ss() { return ss.clone(); }
    public boolean equals(Object o) { 
        return o instanceof Foo f && Arrays.equals(f.ss, ss);
    }
    public int hashCode() { return Objects.hash(Arrays.hashCode(ss)); }
}

What this does is a defensive copy on the way in (in the constructor) and on the way out (in the accessor), as well as adjusting the equality semantics to use the contents of the array. This supports the invariant, required in the superclass java.lang.Record, that "taking apart a record into its components, and reconstructing the components into a new record, yields an equal record."

You might well say "but that's too much work, I wanted to use records so I didn't have to type all that stuff." But, records are not primarily a syntactic tool (though they are syntactically more pleasant), they are a semantic tool: records are nominal tuples. Most of the time, the compact syntax also yields the desired semantics, but if you want different semantics, you have to do some extra work.

2 of 3
14

List< Integer > workaround

Workaround: Use a List of Integer objects (List< Integer >) rather than array of primitives (int[]).

In this example, I instantiate an unmodifiable list of unspecified class by using the List.of feature added to Java 9. You could just as well use ArrayList, for a modifiable list backed by an array.

record Foo( List < Integer >integers ) {}

List< Integer > integers = List.of( 1 , 2 );
var foo = new Foo( integers );

System.out.println( foo ); // Foo[integers=[1, 2]]
System.out.println( new Foo( List.of( 1 , 2 ) ).equals( new Foo( List.of( 1 , 2 ) ) ) ); // true
System.out.println( new Foo( integers ).equals( new Foo( integers ) ) ); // true
System.out.println( foo.equals( foo ) ); // true
🌐
Medium
medium.com › javarevisited › record-keyword-in-java-14-updated-2023-2c0a7b970974
Record Keyword in Java 14. Get rid of boilerplate code for… | by BaseCS101 | Javarevisited | Medium
February 5, 2024 - package com.basecs101.model; import java.util.Objects; /** * A simple POJO clas with 3 fields */ public class Person { private String firstName; private String lastName; private int age; public Person(String firstName, String lastName, int age) { this.firstName = firstName; this.lastName = lastName; this.age = age; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; }…
🌐
Laytoun' thoughts!
aboullaite.me › java-14-records
Java 14 new features: Records - Mohammed Aboullaite
March 22, 2020 - This is the second article in the blog post series discussing the new features introduced in java 14. Today's article is focused on Records that aims to provide a compact & concise way for declaring data classes. Java 14 rew features articles: * Pattern Matching for instanceof, jpackage & helpful NPEs * Switch Expressions,
🌐
Medium
medium.com › @random_developer › records-in-java-9cd34d478c96
Java 14 Records with Example. This article is divided into the… | by Random developer | Medium
November 30, 2024 - Record was introduced as a part of Java 14 and has evolved over the years. A record is simply an immutable class.
🌐
Devmio
devm.io › java › java-14-records-deep-dive-169879
Hands on with Records in Java 14 – A Deep Dive
March 26, 2020 - To celebrate the release of Java 14, here’s a deep dive into Records in JDK 14. It’s written by Developer Advocate at JetBrains, founder of eJavaGuru.com and Java Champion, Mala Gupta. What a treat! So let's get stuck in.
🌐
Carlos Chacin
carloschac.in › 2020 › 04 › 17 › java-records
🚀 Java 14 Records 💾 (Preview) | Carlos Chacin
April 17, 2020 - public record Person( String firstName, String lastName, String address, LocalDate birthday, List<String> achievements) { } ... <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <release>14</release> <compilerArgs>--enable-preview</compilerArgs> </configuration> </plugin> ... public final class Person extends java.lang.Record { private final java.lang.String firstName; private final java.lang.String lastName; private final java.lang.String address; private final java.time.LocalDate birthday; private final java.util.List<java.lang.String> achieveme
🌐
Medium
medium.com › @firoudreda01 › introduction-to-java-records-41ecd9522187
Introduction to Java Records. Java Records, introduced in Java 14 as… | by Firoud reda | Medium
November 17, 2024 - Java Records, introduced in Java ... Java 16, provide a compact way to define immutable data classes and offer a concise syntax for creating classes that primarily hold data, without the need for verbose boilerplate code...
🌐
Hacker News
news.ycombinator.com › item
Java 14 Feature Spotlight: Records | Hacker News
February 7, 2020 - 1. Why not just call these "structs"? Everyone knows what a struct is. Also making "struct" a reserved keyword is less likely to cause issues than "record"; · 2. I get that these are trying to be simple but why can't I compose records from other records? I guess I can have a record member ...
🌐
GeeksforGeeks
geeksforgeeks.org › java › what-are-java-records-and-how-to-use-them-alongside-constructors-and-methods
What are Java Records and How to Use them Alongside Constructors and Methods? - GeeksforGeeks
June 22, 2022 - Only 2 lines of code, that is all you need to implement those 80 lines of code using Record. To know how Java implements such a feature, we are going to learn how to set it up ourselves first. Now let us discuss the steps with visual aids demonstrating java records. Since Records is a feature of Java SE 14 we would need JDK 14 on our machine.
Top answer
1 of 1
22

With Java 14, records couldn't have multiple constructors (reference: Java 14 - JEP 359: Records (Preview)).

As of Java 15 and 16+, records may have multiple constructors. (see Java 15 - JEP 384: Records (Second Preview) and Java 16 - JEP 395: Records (Final)).

However, every constructor must delegate to the record's canonical constructor which can be explicitly defined or auto-generated.

An example:

public record Person(
    String firstName,
    String lastName
) {
    // Compact canonical constructor:
    public Person {
        // Validations only; fields are assigned automatically.
        Objects.requireNonNull(firstName);
        Objects.requireNonNull(lastName);

        // An explicit fields assignment, like
        //   this.firstName = firstName;
        // would be a syntax error in compact-form canonical constructor
    }

    public Person(String lastName) {
        // Additional constructors MUST delegate to the canonical constructor,
        // either directly:
        this("John", lastName);
    }

    public Person() {
        // ... or indirectly:
        this("Doe");
    }
}

Another example:

public record Person(
    String firstName,
    String lastName
) {
    // Canonical constructor isn't defined in code, 
    // so it is generated implicitly by the compiler.

    public Person(String lastName) {
        // Additional constructors still MUST delegate to the canonical constructor!
        // This works: 
        this("John", lastName);

        // (Re-)Assigning fields here directly would be a compiler error:
        // this.lastName = lastName; // ERROR: Variable 'lastName' might already have been assigned to
    }

    public Person() {
        // Delegates to Person(String), which in turn delegates to the canonical constructor: 
        this("Doe");
    }
}
🌐
Medium
medium.com › @mak0024 › a-comprehensive-guide-to-java-records-2e8edcbd9c75
A Comprehensive Guide to Java Records | by AM | Medium
January 4, 2024 - The field name and the accessor name are the same as the name of the component. In the record Person, name and age are record components. ... You can notice that the compiler has automatically generated all the necessary methods like hashcode, equals and others. You also might notice that class Person extends a java.lang.Record class.