Records in Java

Naveen AnamaneniUpdated · 5 min read

The lesson "How to Create an Immutable Class in Java" built an immutable Money class by hand: private final fields, a constructor, accessors, equals, hashCode and toString. That's about forty lines for "an amount and a currency". A record (standard since Java 16) says the same thing in one line: record Money(long paise, String currency) {}. The compiler writes everything else, correctly, every time. Records are now the everyday way to model plain data in Java.

One Line, Many Members

The compiler writes the class for you
Records.java▶ Try it
import java.util.HashSet;
import java.util.Set;
 
public class Records {
    record Money(long paise, String currency) {
        static final Money ZERO_INR = new Money(0, "INR");
 
        Money plus(Money other) {
            return new Money(paise + other.paise, currency);
        }
 
        String display() {
            return currency + " " + paise / 100 + "."
                + String.format("%02d", paise % 100);
        }
    }
 
    public static void main(String[] args) {
        Money ticket = new Money(25000, "INR");
        Money same = new Money(25000, "INR");
        System.out.println(ticket);
        System.out.println(ticket.paise() + " " + ticket.currency());
        System.out.println(ticket.equals(same) + " " + (ticket == same));
 
        Set<Money> seen = new HashSet<>();
        seen.add(ticket);
        System.out.println(seen.contains(same));
 
        System.out.println(ticket.plus(new Money(12050, "INR")).display());
        System.out.println(Money.ZERO_INR);
    }
}
Output
Money[paise=25000, currency=INR]
25000 INR
true false
true
INR 370.50
Money[paise=0, currency=INR]

How it works

  1. record Money(long paise, String currency) declares two components. From them, the compiler generates private final fields, a constructor, accessors, equals, hashCode and toString.
  2. Accessors are named after the components, without get: ticket.paise(), not getPaise().
  3. toString prints Money[paise=25000, currency=INR].
  4. equals compares every component, so two records with the same data are equal (though they're still different objects, so == is false). hashCode matches it, which makes records safe in HashSet and as HashMap keys.
  5. You can add methods (plus, display) and static fields and methods (ZERO_INR).

What the Compiler Generates

javap -p on record Point(int x, int y) {} shows it all:

javap -p Point
public final class Point extends java.lang.Record {
  private final int x;
  private final int y;
  public Point(int, int);
  public final java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int x();
  public int y();
}

The Rules

RuleWhat happens if you break it
A record is implicitly finalIt can't be extended
It implicitly extends java.lang.Recordextends isn't allowed: error: '{' expected
Components are private finalAssigning one: cannot assign a value to final variable x
No extra instance fieldserror: field declaration must be static
Static fields, static methods and instance methodsAllowed
Implementing interfacesAllowed: record Money(...) implements Comparable<Money>
Nested and local recordsAllowed, and implicitly static

You can also write a component's accessor yourself (for example, to return a copy), as long as it keeps the same name and return type.

When to Use a Record

Good fitPoor fit
Values: money, coordinates, date rangesObjects whose state changes over time: a bank account balance
Data carriers: rows from a query, API requests and responsesEntities that need inheritance
Map keys and set elementsClasses that must hide some of their data
Returning several values from a methodClasses with complex lifecycles

Common Mistakes

Calling getX(). Record accessors are x().

Expecting a mutable List component to be protected. Copy it with List.copyOf in a compact constructor.

Using records for objects with identity and changing state. An account whose balance changes is a class, not a record.

Interview Corner

What is a record in Java?

A record, standard since Java 16, is a special kind of class for immutable data. Declaring record Point(int x, int y) {} generates a final class extending java.lang.Record with private final fields, a canonical constructor, accessor methods named after the components, and equals, hashCode and toString based on all components. Records can't extend other classes or declare extra instance fields, but they can implement interfaces and add methods and static members.

Is a record fully immutable?

Only shallowly. Its fields are final, so they can't be reassigned, but if a component refers to a mutable object such as an ArrayList or an array, that object's contents can still change. Copy such components into immutable forms in a compact constructor.

Practice

  1. Easy: Write record Student(String name, int marks) and print a student, their name and whether two students with the same data are equal.
  2. Harder: Write record DateRange(LocalDate start, LocalDate end) with a method long days() that returns the number of days between them, and a static factory ofWeek(LocalDate start).

Quick check: Records

4 questions
1What is the accessor for component paise in record Money(long paise, ...)?
2Can a record extend another class?
3Two records with the same component values are:
4Can a record declare an extra instance field?

Key Takeaways

Was this tutorial helpful?

Lesson 174 of 482