What's new in Java 15: Record
Tags: java
Published: 2020-09-30 22:24:28

In Part II of the "What's new in Java 15" series, we will explore what a Java Record is, compare it to existing language features and other languages, and look at how it can be used in a Spring Boot application to simplify the code.

## What is a Java Record
A Java `Record` is a new way to declare a type in the Java language. Records were introduced to the Java language to reduce the boilerplate associated with Plain Old Java Objects (POJO). When creating a good POJO, a developer must implement an `equals` method, a `toString` method, and the corresponding getters. From POJO to POJO, the implementations are exactly the same, the only thing that changes is the name of the properties for the type. Although IDEs and projects like [Lombok](https://projectlombok.org/) have created features which auto generate this boilerplate code, having all of this boilerplate can get in the way of understanding what the POJO represents. 

A `Record` does the following: 
- generates one public constructor with all of the properties
- marks all properties as `private final`
- creates public getter methods for all properties
- creates a `toString`, `equals`, and `hashCode` method
- allows properties to be decorated with annotations

A `Record` cannot do the following: 
- declare any other instance variables
- extend any other class

Java's `Record` type is very similar to [Kotlin's Data Classes](https://kotlinlang.org/docs/reference/data-classes.html). One noticeable difference in the API generated by Java and compared to the one generated by Kotlin is Java's `Record` lacks a copy function. 

To read more about a `Record`, checkout Java's [documentation](https://openjdk.java.net/jeps/359#:~:text=Records%20are%20a%20new%20kind,API%20that%20matches%20that%20representation.) on it.

## The Code
Alright, enough talk, let's take a look at some code. 

The following code snippet is a traditional implementation of a POJO using classes. For this example, we will be modeling a person. A person has two attributes, name and occupation. 

```java
import java.util.Objects;

public class Person {
    private final String name;
    private final String occupation;

    public Person(String name, String occupation) {
        this.name = name;
        this.occupation = occupation;
    }

    public String getName() {
        return name;
    }

    public String getOccupation() {
        return occupation;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) &&
                Objects.equals(occupation, person.occupation);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, occupation);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", occupation='" + occupation + '\'' +
                '}';
    }
}

```

As is evident from the above code, creating a good POJO with getters, `equals`, `hashCode`, `toString` and a constructor is very verbose. To implement a POJO with two properties was 47 lines of code. POJOs in the wild will almost certainly have more than two properties. This POJO also has a huge maintenance cost. Each time a new property is added, the constructor, getters, `equals`, `hashCode`, and `toString` methods all have to be modified.

Lombok attempted to solve this problem with the [@Value annotation](https://projectlombok.org/features/Value). The same person class written with Lombok: 

```java
import lombok.Value;

@Value
public class Person {
    String name;
    String occupation;
}
```

This code is much simpler. It reduces the boilerplate by hiding it behind the `@Value` annotation and it eliminates any maintenance cost associated with this POJO. However, the `@Value` annotation is not a native language feature and requires a new dependency to be added to the project. Lombok can also be confusing to new developers on a codebase. 

Java Records allow for the Person class to be modeled in a succinct way and are included in the native language. 

To declare a record, give it a name and declare the properties on the record. That's it. Java will take care of the rest. No boilerplate. No sifting through 50 lines of code to figure out what data the class models. 

```java
public record Person(String name, String occupation) {}
```

Records can be used in the following ways: 
```java
Person p1 = new Person("test1", "software engineer");
Person p2 = new Person("test2", "test engineer");
Person p3 = new Person("test1", "software engineer");

System.out.println(p1.toString()); // Person[name=test1, occupation=software engineer]
System.out.println(p1.name()); // "test1"
System.out.println(p1.occupation()); // "software engineer" 
System.out.println(p1.equals(p2)); // false
System.out.println(p1.equals(p3)); // true
```

## Real Life Examples
### POST Request Body
We have now learned about what a `Record` is and how to use it. But how can we actually use a `Record` in the wild? When creating a REST endpoint with Spring Boot and Spring Web, POST Request Bodies are often declared as a POJO. A `Record` class can be used to simplify the code needed to model the request body. The following example declares a REST controller and a POST endpoint which accepts the Person model from earlier in the request body. 

```java
// PersonController.java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/person")
public class PersonController {

    @PostMapping
    public void post(@RequestBody PersonRequest person) {
        System.out.println(person);
    }
}

// PersonRequest.java
public record PersonRequest(String name, String occupation) {}
```

Starting up the application and making the following curl request results in a response code of 200 OK and a log message with the person request on the console.
```sh
curl --location --request POST 'localhost:8080/person' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name": "test1",
    "occupation": "Software Engineer"
}'
```

Since records can be decorated with annotations, we can make use of the Spring Validation Framework annotations. 

```java
// PersonController.java
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/person")
public class PersonController {

    @PostMapping
    public void post(@Validated @RequestBody PersonRequest personRequest) {
        System.out.println(personRequest);
    }
}

// PersonRequest.java
import javax.validation.constraints.NotNull;

public record PersonRequest(@NotNull String name, @NotNull  String occupation) {}
```

Now, when making a request with a missing name, there will be a status code of 400 Bad Request returned since we have required name to be on the request body with the `@NotNull` annotation. 
```sh
curl --location --request POST 'localhost:8080/person' \
--header 'Content-Type: application/json' \
--data-raw '{
    "occupation": "Software Engineer"
}'
```

## Conclusion
In this post we learned about what a Java `Record` is, learned how to use it, and went through an example of how to use a `Record` in the real world. An example Spring Boot application can be found with test cases on my [GitHub profile](https://github.com/jackcmeyer/java15demo/commit/a90bd69f32d27447a5cbff6844de37eb011a5b25).