Records in JDK 14 (JEP 359)

Ashish Garg
6 min readJan 22, 2020

--

Photo by João Silas on Unsplash

Overview

JEP 359 (Records) added as preview feature in JDK 14 (Due for release in March 2020). Records are part of larger release named as project Amber (Umbrella project having multiple feature targeted in various releases).

Records are primarily designed to keep in mind to target the case where classes are getting created in various application, just to hold data. Usually in beginning developer create a data aggregate class with the pure intention to hold data but during course of project it got corrupted with various business logic (functional methods) and loose its flavour of pure data aggregate and become a nightmare to manage.

Let’s say in today’s world developer like to create a data aggregate, he will create a class something like below with required fileds. I have created a Fund class with some fileds like nameOfFund, IdOfFund etc.

Data Aggregate

As you might have noticed this is a simple class with different fileds which represent the state of this class but for a developer who will read it later there is no explicit communication from language semantics that this is a data aggregate not a Java Bean. Above class didn’t have certain required methods i.e. equals(), hashCode(), toString(), Constructors and Getters to make it useful.

Writing all these methods introduce a lot of boilerplate code for a developer to write, even most of IDEs in today’s world can generate these methods still it’s a brain exercise for developer who has to read it and there is always a possibility at some day developer will add/remove certain fields from it but did he rightly update all relevant method will always be question?

Let’s see how a Fund class will look like after all required methods generated by our favourite IDE (I have used IntelliJ Community Edition to generate these methods)

Data Aggregate with methods

Record (a preview feature in JDK14)

To address such concerns JEP 359 comes with a new type declaration record like enum we already have. Records as name specify is purely mean for having/holding data without any functionality. It also help to reduce a lot of boilerplate code which developer need to write to create a data aggregate. In today’s world there is no mean to communicate that developer create a class with the pure intention of data aggregate, record will make this communication clear.

record Fund(String nameOfFund, String idOfFund, FundType typeOfFund, float unitPrice, String nameOfFundManager,
float exitLoad, float aum, boolean lockIn) {}

In above code snippet record type has been specified just before name and required fields has been placed with their type under braces () that’s all you need to do as a developer to create data aggregate. Record comply with the property of immutable class.

As mentioned in effective java also “ Classes should be immutable unless there’s a very good reason to make them mutable….If a class cannot be made immutable, limit its mutability as much as possible” Generally to make a immutable class developer need to make sure:

  • Class is non extendable — make the class final, or use static factories and keep constructors private.
  • Make fields private final
  • Do not any declare any method which can change state of object after creation.

In case of record Compiler will create a Immutable class based on record header declaration with below feature:

  • private final field for each component (field)
  • Getter for each component with the same name and type as specified in state
  • A public constructor with the same name as record and having all fields with type.
  • Implementation of equals() and hashCode().
  • Implementation of toString().

If you want to see what all methods has been created by compiler simply run javap on *.class file (Compilation instructions has been provided at the end of this article)

Result of javap

In above snapshot your can see record class is final and extending java.lang.Record

Restriction on Records

  • Records cannot extend any other class
  • Records cannot declare instance field
  • Records are implicitly final and cannot be abstract
  • Records are immutable as all state components are final.

Reflection

Two new methods has been added in reflection API for records.

  • getRecordComponents() — Returns an array of RecordComonent (This is a new class added in JDK representing a record component) objects representing all the record components of this record class, or null if this class is not a record class. Components will be returned in the same order as declared while creating record. After getting array of RecordComponent you can iterate individual element and access name, type etc.
  • isRecords() — Returns true if and only if this class is a record class.

As applicable with developer created Immutable class you can change value of a particular filed via reflection.

Field fld = null;
try {
fld = fund.getClass().getDeclaredField("aum");
fld.setAccessible(true);
fld.setFloat(fund,120f);
System.out.println(fund.aum);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}

As shown in above sample, you just need to call setAccessible(true) to update record state. In my opinion as Java added a new type, it shouldn’t allow record state to be change via reflection but that’s my personal opinion JDK designer must be some better reason to allow it.

Serialization

Record support Serialization but there are certain changes has been done in JDK.

  • Record components/fields are deserialized prior to the invocation of record constructor.
  • For normal class while serialize/deserialize we can override certain methods (writeObject, readObject etc.) to specify custom behaviour. Its not allowed in record.
  • The serialVersionUID of a record class is 0L unless explicitly declared
  • While deserialization system didn’t match value of serialVersionUID.

Important points

  • As compiler will create getter method with the same name as field, developer need to conscious enough to provide a good context specific readable name.
  • Record body may declare static methods, static fields, static initializers, constructors, instance methods, and nested types.
  • There is very important difference between a class and record.

A class can have field ‘name’, constructor can have an argument ‘name’ and a getter method can have name; all three exist without any relationship but in case of record all three will be referring to same component/field. Here is sample class where there is no relationship between instance field ‘name’, constructor argument ‘name’ and method ‘getname()’.

public class Sample {

private static String name = "Fun";

Sample(){
}

Sample(String name){
System.out.println(name);
}

private String getname(){
return "Ashish";
}

public static void main(String[] args) {
System.out.println(name);
System.out.println(new Sample().getname());
new Sample("Dummy");
}
}

if you run above program you will see a output where three different values are getting print on console.

  • You can’t create a subclass of java.lang.Record, any such attempt will throw error “records cannot directly extend Record”.
  • As mentioned above compiler will create a canonical constructor for you based on the definition provided in record header but there may be case where you like to put kind of validation it has been allowed
public Fund{
if (nameOfFund==null || idOfFund==null){
throw new IllegalArgumentException("Custom exception");
}
}
  • Record should be used where you are missing struct in Java.
  • Record will remove the need of libraries like Lombok.
  • Record and Inline classes (part of project Valhalla) are two very different things.

Compile and Run

As record released as preview feature with JDK 14, you need to enable preview to play around with it. To compile a class follow command like below

javac --enable-preview --release 14 <Path of Java Source File>

it will compile and create class. Now to run it simply use java command with enable preview.

If you want to leverage the JDK 11 feature where a single source file can be directly compile and run with single java command follow below command.

java --enable-preview --source 14 <Path of Java Source File>

--

--

Ashish Garg

Father, amateur photographer, software geek. Love all my hats