Shiklo Programming
EN
  • English
  • Español
  • Português
  • Deutsch
  • Français
  • 日本語
  • हिन्दी

Java Records

Many classes only carry data: a point with an x and a y, a book with a title and an author, a result with a name and a score. Writing them in full means a constructor, private final fields, getters, a toString, and the equals and hashCode methods that collections need. A record (Java 16 and later) gives you all of that in one line.

A record in one line

record Point(int x, int y) {
}

public class Main {
    public static void main(String[] args) {
        Point p = new Point(3, 4);
        System.out.println(p);
        System.out.println(p.x() + p.y());
        Point q = new Point(3, 4);
        System.out.println(p.equals(q));
        System.out.println(p == q);
    }
}
Output
Point[x=3, y=4]
7
true
false

From record Point(int x, int y) Java creates:

  • private final fields x and y, which can never change,
  • a constructor that takes both values,
  • accessor methods x() and y() (without the get prefix),
  • a readable toString, and equals and hashCode that compare the values.

Two records with the same values are equals, even though they are different objects (== is false). That is what makes records safe to use as keys in a HashMap.

Checking values in the constructor

A record can have a compact constructor: a constructor without a parameter list that runs before the fields are set. It is the place to check the values, the idea you met in Encapsulation:

record Temperature(double celsius) {
    Temperature {
        if (celsius < -273.15) {
            throw new IllegalArgumentException("Below absolute zero: " + celsius);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new Temperature(21.5));
        try {
            new Temperature(-300);
        } catch (IllegalArgumentException e) {
            System.out.println("Refused: " + e.getMessage());
        }
    }
}
Output
Temperature[celsius=21.5]
Refused: Below absolute zero: -300.0

throw and try ... catch are explained in Exceptions and Throw and Throws.

Methods in a record

A record can have its own methods and static methods. Because the fields never change, a method that "changes" something returns a new record, just like the string methods do:

record Money(long paise) {
    static Money rupees(double r) {
        return new Money(Math.round(r * 100));
    }

    Money plus(Money other) {
        return new Money(paise + other.paise);
    }

    @Override
    public String toString() {
        return String.format("Rs %d.%02d", paise / 100, paise % 100);
    }
}

public class Main {
    public static void main(String[] args) {
        Money price = Money.rupees(149.50);
        Money tax = Money.rupees(7.48);
        System.out.println(price.plus(tax));
    }
}
Output
Rs 156.98

Records with arrays of data

Records work nicely with arrays and loops when you process a set of results:

record Result(String name, int score) {
    boolean passed() {
        return score >= 40;
    }
}

public class Main {
    public static void main(String[] args) {
        Result[] results = {new Result("Aman", 72), new Result("Bina", 35), new Result("Chetan", 58)};
        for (Result r : results) {
            System.out.println(r.name() + (r.passed() ? " passed" : " failed"));
        }
    }
}
Output
Aman passed
Bina failed
Chetan passed

Use a record whenever a class is "just data that does not change". Use a normal class when the object must change over time or needs to hide how it stores its data.

Practice exercises

  1. Write a record Book(String title, String author, int year) and print two books.
  2. Add a compact constructor that refuses a negative year.
  3. Add a method age() that returns how many years ago the book appeared (use the year 2025).