Records in JDK16 (JEP 395)
Published: 2020-02-29 14:50:11

## Overview

JEP 359 (Records) added as preview feature in JDK 14 and stable production ready feature with JEP 395 in 
JDK 16. Records are part of larger release named as project 
[Amber](https://openjdk.java.net/projects/amber/) (Umbrella project having multiple feature 
targeted in various releases).

Records are primarily designed to target the use-case where classes are getting created, just to 
hold data (DTOs). 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 lost its flavour of pure data aggregate and become a nightmare to manage.
You might be thinking of using Record in place of JPA Entities, why it's not possible I will 
explain later.

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

```java
public class Fund {

    private String nameOfFund;
    private String IdOfFund;
    private FundType typeOfFund;
    private float unitOfPrice;
    private String nameOfFundManager;
    private float exitLoad;
    private float aum;
    private boolean lockIn;
}

enum FundType{
    LARGE_CAP,
    MID_CAP,
    SMALL_CAP,
    MULTI_CAP,
    HYBRID
}
```

As you might have noticed, this is a simple class with different fields representing state of 
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, also it does not 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 the 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 (equals & hashCode) 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*)

```java
public class Fund {

    private String nameOfFund;
    private String IdOfFund;
    private FundType typeOfFund;
    private float unitOfPrice;
    private String nameOfFundManager;
    private float exitLoad;
    private float aum;
    private boolean lockIn;

    public Fund(String nameOfFund, String idOfFund, FundType typeOfFund, float unitOfPrice, String nameOfFundManager, float exitLoad, float aum, boolean lockIn) {
        this.nameOfFund = nameOfFund;
        IdOfFund = idOfFund;
        this.typeOfFund = typeOfFund;
        this.unitOfPrice = unitOfPrice;
        this.nameOfFundManager = nameOfFundManager;
        this.exitLoad = exitLoad;
        this.aum = aum;
        this.lockIn = lockIn;
    }

    public String getNameOfFund() {
        return nameOfFund;
    }

    public void setNameOfFund(String nameOfFund) {
        this.nameOfFund = nameOfFund;
    }

    public String getIdOfFund() {
        return IdOfFund;
    }

    public void setIdOfFund(String idOfFund) {
        IdOfFund = idOfFund;
    }

    public FundType getTypeOfFund() {
        return typeOfFund;
    }

    public void setTypeOfFund(FundType typeOfFund) {
        this.typeOfFund = typeOfFund;
    }

    public float getUnitOfPrice() {
        return unitOfPrice;
    }

    public void setUnitOfPrice(float unitOfPrice) {
        this.unitOfPrice = unitOfPrice;
    }

    public String getNameOfFundManager() {
        return nameOfFundManager;
    }

    public void setNameOfFundManager(String nameOfFundManager) {
        this.nameOfFundManager = nameOfFundManager;
    }

    public float getExitLoad() {
        return exitLoad;
    }

    public void setExitLoad(float exitLoad) {
        this.exitLoad = exitLoad;
    }

    public float getAum() {
        return aum;
    }

    public void setAum(float aum) {
        this.aum = aum;
    }

    public boolean isLockIn() {
        return lockIn;
    }

    public void setLockIn(boolean lockIn) {
        this.lockIn = lockIn;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Fund)) return false;
        Fund fund = (Fund) o;
        return Float.compare(fund.unitOfPrice, unitOfPrice) == 0 &&
                Float.compare(fund.exitLoad, exitLoad) == 0 &&
                Float.compare(fund.aum, aum) == 0 &&
                lockIn == fund.lockIn &&
                nameOfFund.equals(fund.nameOfFund) &&
                IdOfFund.equals(fund.IdOfFund) &&
                typeOfFund == fund.typeOfFund &&
                nameOfFundManager.equals(fund.nameOfFundManager);
    }

    @Override
    public int hashCode() {
        return Objects.hash(nameOfFund, IdOfFund, typeOfFund, unitOfPrice, nameOfFundManager, exitLoad, aum, lockIn);
    }

    @Override
    public String toString() {
        return "Fund{" +
                "nameOfFund='" + nameOfFund + '\'' +
                ", IdOfFund='" + IdOfFund + '\'' +
                ", typeOfFund=" + typeOfFund +
                ", unitOfPrice=" + unitOfPrice +
                ", nameOfFundManager='" + nameOfFundManager + '\'' +
                ", exitLoad=" + exitLoad +
                ", aum=" + aum +
                ", lockIn=" + lockIn +
                '}';
    }
}

enum FundType{
    LARGE_CAP,
    MID_CAP,
    SMALL_CAP,
    MULTI_CAP,
    HYBRID
}
```

## Record

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 helps 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 
created a class with the pure intention of data aggregate, record will make this communication very 
crystal clear.

```java
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 an immutable class developer need to make sure:

* Class must not be 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

As definition a record class declaration consists of a name, optional type parameters, a header, 
and a body. The header lists the components of the record class, which are the variables that 
make up its state. In case of record Compiler will create an Immutable class based on record header declaration 
with below features:

* For each component in header there will be a private final field and Getter for each component with the same name 
  and type as specified in state
* A canonical 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 have been created by compiler simply run javap on *.class file (Compilation instructions has been provided at the end of this article)

![](https://dev-to-uploads.s3.amazonaws.com/i/561e738s4dwl0iiqekgq.png)

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

## Rules for Constructor

* A normal class without any explicit constructor declaration got default constructor from java 
compiler that's not true for record; record class without any constructor declarations is 
automatically given a canonical constructor that assigns all the private fields to the 
corresponding arguments.
* Canonical constructor may be declared explicitly with a list of formal parameters which match 
the record header.
* There is another form of constructor defined in Record named as *Compact canonical* constructor 
the parameters are declared implicitly, and the private fields corresponding to record components 
cannot be assigned in the body but are automatically assigned to the corresponding formal parameter
(this.x = x;) at the end of the constructor. The compact form helps developers focus on 
validating and normalizing parameters without the tedious work of assigning parameters to fields.
* A record class can be declared top level or nested, and can be generic.
* A record class can declare static methods, fields, and initializers.
* A record class can declare instance methods.
* A record class can implement interfaces and declare instance methods to implement them.
* A record class can declare nested types, including nested record classes. If a record class 
is itself nested, then it is implicitly static; this avoids an immediately enclosing instance 
which would silently add state to the record class.
* Instances of record classes can be serialized and deserialized. However, the process cannot 
be customized by providing writeObject, readObject, readObjectNoData, writeExternal, or 
readExternal methods. The components of a record class govern serialization, while the 
canonical constructor of a record class governs deserialization.

## Restriction on Records

- Record class cannot extend any other class. The superclass of your record class will always 
be java.lang.Record, even though a normal class can explicitly extend and there implicit superclass is 
java.lang.Object.
- Records cannot declare instance field and cannot contain instance initializers.
- Records are implicitly final and cannot be abstract
- Records are immutable as all state components are final.
- Any explicit declaration of member must match the type of the automatically derived member. 
- Any explicit implementation of accessors or the equals or hashCode methods should be careful 
to preserve the semantic invariants of the record class 
- A record class cannot declare native methods. If a record class could declare a native method
then the behavior of the record class would by definition depend on external state rather than 
the record class's explicit state.

## Local Record classes

* Local records are implicitly static this is in contrast of local classes which are not 
implicitly static.

## 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 return 
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.

```java
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 did not match value of serialVersionUID.


## Record with Sealed classes

Record work pretty well with upcoming feature Sealed classes (It's under preview right now).

```java
    sealed interface MutualFund permits LargeCap,MidCap,SmallCap {}
        record LargeCap(double AUM) implements MutualFund{}
        record MidCap(double AUM) implements MutualFund{}
        record SmallCap(double AUM) implements MutualFund{}
```

## 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()’.

```java
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

```java
public Fund{
    if (nameOfFund==null || idOfFund==null){  
        throw new IllegalArgumentException("Custom exception");
    }
}
```

- Record should be used where you are missing [struct](https://www.programiz.com/c-programming/c-structures) in Java.
- Record will remove the need of libraries like Lombok.
- Record and Inline classes (part of project Valhalla) are two very different things.