Java 16 Records

This article will cover a new feature introduced in java 16, Records: its use, syntax, creating objects, in detail with example programs and explanation.

What is record
Record is a new type declaration construct added in java 16. It is a short and simple way of declaring a class like structure.

A java bean or POJO looks as below

public class Employee {

  private String name;

  private String id;

  public Employee(String name, String id) {
    this.name = name;
    this.id = id;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setId(String id) {
   this.id = id;
  }

  public String getId() {
    return id;
  } 

  public String toString() {
    return "[" + name +", " + id + "]";
  }

  public boolean equals(Object o) {
    // implementation
  }

  public int hashCode() {
    // implemenation
  }
  
}

This class has a constructor, fields or instance variables, their getter and setter methods and overridden equals() and hashCode() methods.

If you closely look at this class, it has a lot of code that is repetitive and boiler plate.

Java 16 records aim to avoid this boiler plate code. It allows you to only declare the fields and generates constructor, getter methods, equals() and hashCode() methods behind the scenes.

So, with java 16, you can define a one-liner record in place of a lengthy class having fields and the boiler plate code written around them.
Java 16 record syntax
A record is created using record keyword followed by the name of record and the fields which it should contain.
A record can be defined with below syntax

public record <record-name> (<fields declaration> { }

Fields of a record are declared in the same way as method parameters. Multiple fields are separated by a comma.

If a record is public, then it should be defined in its own file, that is, the name of the file should be the same as the record name.
This is similar to class declaration rule in java.

Java 16 record example
Below is an Employee record having two fields name and id.

public record Employee(String name, String id) { }

This is a replacement of the Employee class defined in the previous section.
This one-liner code with generate a class like structure having name and id fields, their getter methods, a constructor and equals() and hashCode() methods.

Below is the decompiled version of Employee record.

Compiled from "Employee.java"
public final class com.example.demo.Employee extends java.lang.Record {
  public com.example.demo.Employee(java.lang.String, java.lang.String);
  public java.lang.String name();
  public java.lang.String id();
  public final java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
}

Following are some important points regarding java 16 records:

1. A record extends inbuilt Record class.
All records in java extend java.lang.Record. This is also analogous to classes in java, which extend java.lang.Object.

2. A record is final class meaning you cannot extend or subclass it.

3. equals() and hashCode() methods are automatically generated. Though, you can override equals() and hashCode() method if you wish to.

4. A record can not extend any other class, but they can implement any number of interfaces.

5. A record can not have any other field declaration inside its body. Any other field declared should be static.
Example,

public record Employee(String name, String id) {
  
  // not allowed
  private String address;

  // allowed
  private static int age;
}

6. If you did notice, all the fields of a record have getter or accessor methods but no setter or mutator methods. This is because records are intended to be immutable by nature.
Immutable means whose state cannot be changed after creation.

7. A record generates a constructor with all its fields as parameters. But, you can add additional constructors as well, such as for imposing some validations. Example,

public record Employee(String name, String id) {

  public Employee(String name, String id) {
    if(name == null || id == null) {
      throw new IllegalStateException();
    }
    this.name = name;
    this.id = id;
  }
}

Above constructor throws and IllegalStateException if name and id are not provided.

8. A record can contain other methods containing business logic as well.
Record objects
In the last section, we saw how to define a record. Now, we will look at how to create objects of a record.

An object of record can be created using new operator followed by the name of the record along with the parameter values it expects.
This is similar to the way we create objects of a class in java. Below is an example,

Employee employee = new Employee("Abc", "1");
System.out.println("Name of employee is: " + employee.name());
System.out.println("Id of employee is: " + employee.id());
System.out.println(employee.toString());

Output is

Name of employee is: A
Id of employee is: 123
Employee[name=A, id=123]

Note that name() and id() are the getter methods corresponding to the fields of the record.

Record equality
Java compiler automatically generates hashCode() and equals() methods for a record. These can be used to compare two record objects.
Two records are considered to be equal if the values of all its fields are same. Internally, equals() compares the values of all the fields of a record and returns true if they are same. Example,

Employee e1 = new Employee("Abc", "1");
Employee e2 = new Employee("Abc", "2");
// e1.equals(e2) -> true
Employee e3 = new Employee("Abc", "2");
// e1.equals(e3) -> false

Reflection support
Java 16 adds two new methods to java.lang.Class to provide support for reflection to records. These are
1. getRecordComponents()
This methods returns an array of RecordComponent class, which contains information about each of the fields of the record such as its name, its methods, annotations etc. Example,

Employee e1 = new Employee("A", "123");
RecordComponent[] recordComponents = e1.getClass().
                                      getRecordComponents();
for (RecordComponent component : recordComponents) {
  System.out.print("Field name: " + component.getName() + 
      ", accessor method name: "
      + component.getAccessor().getName());
}

This prints

Field name: name, accessor method name: name
Field name: id, accessor method name: id

getRecordComponents() returns null when invoked on a class object other then record.
2. isRecord()
Returns true if the class is of type record. Example,

Employee e = new Employee("A", "123");
// e.getClass().isRecord() -> true

In this article, we saw that a record is a class like structure that avoids a lot of boiler plate code in a java class.
A record can be easily used as a replacement of a class, if that class is intended to be used as a plain java object that holds values.
This will make the code short and clean.

Hope the article was helpful.