Java Records

Sep 11, 2021 · 6 mins read

A Record is a special form of class in Java like the enums. It was released in Java 16, after being introduced as a preview feature in Java 14.

Records provides a way to create data-only classes with minimal syntax, avoiding the need to write a lot of boilerplate code.

For example, prior to Records, here’s how one would define a data-only classes (POJO) to represent a Point in the x-y plane.

class Point {
    private double x;
    private double y;

    public Point(double x, double y) {
        // initialize
    }

    public double getX() {
        return x;
    }

    public double getY(){
        return y;
    }

    public boolean equals(Object obj) {
        //  Tests whether or not this object is equal to the specified object.
    }

    public int hashCode() {
        // Calculate and return hashcode
    }

    public String toString() {
        // Return string representation of this object.
    }
}

This class is a pain to write. Even if you argue you can get the IDE to auto-generate most of it or use Lombok, it’s still a pain to read. In fact, it doesn’t need to be read at all.

You can represent the Point class (above) as a record with much less ceremony:

record Point(double x, double y) { }

That’s it. With just one line of code, we expressed the same verbose POJO class. Clean!

A record:

  • consists of a name. In this case, Point.
  • has a list of record components. In this case, double x and double y.

What You Get with Records

Declaring Point as record does the following:

  • declares a final class (cannot be extended, cannot be abstract)
  • declares ‘immutable’ instances variables from the record components. For Point, it will declare x and y as private final, accessible using p.x() and p.y(), not p.getX() or p.getY(). Note: The immutability is shallow; if the instance fields are objects, their state is not regulated and could be changed.
  • provides a canonical constructor which sets all the instance fields.
  • provides equals(), hashCode() and toString() methods.
  • Serialization, provided you implement the Serializable e.g. record Point(double x, double y) implements Serializable {}

Syntactic Sugar over Flexibility

At the start of this article, I mentioned that records are a special form of class. This is true and to further refine the definition, you can think of them as a restricted kind of class like the enums. In both cases, we give up some flexibility and in return get syntactic sugar or semantic benefits. As developers, we can choose whether we want the flexibility given the use case or syntactic benefits.

Breaking Encapsulation

By coupling or binding the API to the representation e.g. p.x(), we expose the internal details, hence breaking encapsulation, a core concept of object-oriented design. But sometimes, for simple cases like the Point class that we defined, encapsulation is an overkill and there’s no need for it, especially when the downside is boilerplate code.

Another argument for p.x() over p.getX() is that the relationship x and p.x() is a compiler level commitment. Whereas x and p.getX() is only by convention.

Constructors

Records provide a canonical constructor out of the box. However, you can customize the constructor. This is often done to validate arguments. For example.

public record WholeNumber(int n) {
    public WholeNumber(int n) {
        if (n < 0)
            throw new IllegalArgumentException(String.format("%d", n));
        this.n = n;
    }
}

This is a common use case and thankfully, Java implementors allowed a more compact and concise form where the arguments to the constructor can be omitted as well as instance field assignment. This is called the compact constructor. The code for initializing instance fields is supplied by the compiler.

public record WholeNumber(int n) {
    public WholeNumber {
        if (n < 0)
            throw new IllegalArgumentException(String.format("%d", n));
    }
}

Instance Methods

Records can have instance methods. In addition, we can also override toString(), equals() etc. methods

public record Integer(int n) {
    public boolean isPositive() {
        return n > 0;
    }
} 

Static Fields and Methods

Records can have static fields and static methods. But only static fields are allowed. Records cannot declare non-static instance fields. The only fields a record will have are those that are defined in the record component. The reason behind this is so that the state of a record can be entirely determined using record components.

public record Integer(int n) {
    public static Integer MAX = new Integer(Integer.MAX_VALUE);
} 

Generics

Generics are allowed too.

public record Comparision<T>(T first, T second) {}

Inheritance

Because the intention of Records is to model data-only classes, inheritance is not supported: Records cannot extend any other class.

  • boolean isRecord(): Returns true if the class was declared as a record.
  • RecordComponent[] getRecordComponents(): Returns an array of record components.

Summary

Records help us reduce boilerplate code by providing a concise way to model data rather than using traditional classes. They make domain classes more concise and easier to read. Some of the use cases such as returning multiple values from methods, compound keys (e.g. new HashMap<Point, String> where Point is a record,) DTOs and more. Records are certainly useful and will be more so with pattern matching, inline classes, primitive objects and more features in the future releases.

This article was written by Umer Mansoor

#java

You May Also Enjoy


If you like this post, please share using the buttons above. It will help CodeAhoy grow and add new content. Thank you!


Comments (3)


GDF

Thank you for the information. I rarely make comments but this one taught me everything I know about records in less than 5 minutes.


Umer M.

Glad to hear that. It’s a simple yet powerful concept. Hope they wider adoption and help get rid of boilerplate, JavaBeans and tons of messy POJOs, DAOs required by frameworks like Hibernate.


Andrew

If you are using versions older than Java 16 on command line, you must enable records during compilation and runtime.

javac --enable-preview --release 14 Point.java

To execute:

java --enable-preview Point


Speak Your Mind