Java Records (Part 1)

Java records - image of old-fashioned record player

Have you been wondering about Java records? With the release of Java 14, we can now use records to easily create immutable data objects.

Very often we have to pass immutable data between objects, components and/or systems. This data could be a result of a database query, or some information from a web service. We refer to this type of data as data transfer objects (DTOs). Domain objects persisted using an ORM framework also fit into this category.

We then need a class that holds this data. The data must be immutable, so we don’t have to worry about synchronization.

This forces us to create a class with the necessary final fields and a number of standard boilerplate methods. Defining a class for every domain object that we want to pass around obviously becomes tedious, repetitive and error-prone.

Our data classes would need the following:

  • private final field for each data member in the class.
  • A getter method for each field. No setters would be needed.
  • A public constructor with an argument for each data field.
  • The three usual canonical methods: equals()hashCode() and toString().

You might find it useful to revisit the previous posts on JavaBeans and Canonical classes for revision.

What is a Java Record?

A record is a class that is intended to hold pure immutable data. By declaring a type as a record, we are clearly expressing our intention that this type represents only data that cannot be altered.

Records were not designed only to reduce boilerplate code. They are also a way to easily and concisely create immutable data classes. We should only use them where we need that behaviour. Records are not meant to generate mutable classes using the JavaBean naming conventions.

Implementation of Java Records

The syntax for declaring a record is simpler and more concise than a normal Java class. It needs only the type and name of the required fields. It is similar to an enum in that it is a restricted class with special semantics.

Here is a simple example of a record modelling a Person domain object/DTO:

record Person(String name, int age, boolean gender) {}

That’s it! Wow!

The private final instance fields and a public constructor with an argument for each field are generated automatically by the Java compiler. The compiler also generates the equals()hashCode(), and toString() methods.

To see what code is generated, we can use the JDK javap disassembler. The default output of javap is the following:

public final class Person extends java.lang.Record {
  public Person(java.lang.String, int, boolean);
  public final java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public java.lang.String name();
  public int age();
  public boolean gender();
}

The constructor and canonical methods have standard method signatures. The accessor methods don’t follow the JavaBeans naming convention, but rather use the field name as the accessor method name.

The following simple code snippet tests the Person record:

Person p = new Person("Fred", 34, true);
Person q = new Person("Mary", 43, false);

System.out.println("Name: " + p.name());
System.out.println("Age: " + p.age());
System.out.println("Gender: " + p.gender());
System.out.println("hashCode: " + p.hashCode());
System.out.println("toString: " + p.toString());
System.out.println("p equal to q? " + p.equals(q));

Records were a preview feature in Java 14, so when we compile a record using Java 14, we need to use the --enable-preview -release 14 options. If we’re using a later version of Java, then we can compile as normal without that option.

Record as a Restricted Identifier

The word record is a restricted identifier similar to var. It isn’t a keyword (yet), which means the following code is valid:

int record = 10;
void record() {}

However, it would probably be a bad idea to use it as an identifier because some future version of Java might promote it to a keyword.

What’s Next?

We’ve just scratched the surface of records. They have a number of additional features, such as allowing us to overload the automatically generated constructors and methods. We can also use generic types in the arguments of records, and we can create static fields and methods.

We’ll look at these features next week. Until then, keep learning!

As always, please share your comments and questions.

Leave a Comment

Your email address will not be published. Required fields are marked *

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.