Java Records in Spring Boot Rest API
Tags: java, java16, records, springboot
Published: 2020-04-17 21:31:55

## Introduction to Records
Java 14 was released in March 2020 and one of the features that drew our attention was Records. Let's start by explaining what a Record is and then proceed with the potential use cases. 

<u>Note: Java 17 is about to be released (September 2021) which is the first LTS version with records support.</u>

From Openjdk JEP359:
> Records provide a compact syntax for declaring classes which are transparent holders for shallowly immutable data.

This feature attempts to reduce the amount of boilerplate code in our applications as you only have to define the fields (equivalent to class fields). Records provide a <i>public constructor</i> (with all arguments), <i>read methods</i> for each field (equivalent to getters) and the implementation of <i>hashCode</i>, <i>equals</i> and <i>toString</i> methods.

This is how a Record and Class, for the same data structure, would look like.

##### Record
```java
public record Person(String name, int age) {
}
```

##### Class
```java
public class Person {
  private String name;
  private int age;

  public Person(String name, int age) {
    ...
  }

  public boolean equals(Object o) {
    ...
  }

  public int hashCode() {
    ...
  }

  public String toString() {
    ...
  }
}
```

As you will notice this is significantly less code given the class' utility.

## Usages
So where could the Records be used then? One potential application is in rest controllers. Sometimes it can be quite frustrating having to create a class only to be used as a return type or a request body in a rest controller.

In the next section we are going to demonstrate how to build a spring boot rest api with Records.

## Demo

The code for this demo could be found here:
{% github psideris89/java-records-demo %}

### <u>Step 1:</u> Create Spring Boot app
You can use <u>spring initialiser</u> (https://start.spring.io/) to create a spring boot project. The configuration for our demo is:

- Project: **Gradle**
- Language: **Java 16**
- Spring Boot: **2.5.4**
- Name: **movies-demo**
- Dependencies: **Spring Web**

Everything else could be left with the default value.

### <u>Step 2:</u> Project configuration
If you are using an IDE you need to use jdk 16. For *Intellij* set the <u>project sdk</u> to **16** and the <u>project language level</u> to **16**.

![intellij](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5s4a8iln0m1zjwzyn0e3.png)

#### Legacy configuration (java 14)
If you are using java 16 you can skip this section.

To use Records with jdk 14 you have to enable the preview features. You might be prompted to accept some terms if you want to use the preview features, so click accept and proceed.

In addition add the following configuration in *build.gradle*. There is similar configuration for maven.

```groovy
compileJava {
	options.compilerArgs += ["--enable-preview"]
}

test {
	jvmArgs(['--enable-preview'])
}
```

### <u>Step 3:</u> Create Rest Controller
Before we create the rest controller we need to define the Records.

##### Director
```java
public record Director(String name, String surname) {
}
```

##### Movie
```java
public record Movie(String id, Director director, Boolean released) {
}
```

Next create a rest controller with 3 endpoints, one to retrieve all the movies, another to retrieve a movie by id and the last one to add a movie (we don't actually save anything as this is to demonstrate deserialisation).

```java
@RestController
public class MoviesController {

    private List<Movie> movies = List.of(
            new Movie("1", new Director("John", "Wick"), true),
            new Movie("2", new Director("Mary", "Poppins"), false),
            new Movie("3", new Director("Jack", "Sparrow"), true),
            new Movie("4", null, false));

    @GetMapping("/movies")
    public List<Movie> getMovies() {
        return movies;
    }

    @GetMapping("/movies/{id}")
    public Movie getMovie(@PathVariable String id) {
        return movies
                .stream()
                .filter(s -> s.id().equals(id))
                .findFirst()
                .orElseThrow(() -> new RuntimeException("Not found"));
    }

    @PostMapping("/movies")
    public Movie addMovie(@RequestBody Movie movie) {
        // Save the movie, be careful as List.of() is immutable
        return movie;
    }
}
```

### <u>Step 4:</u> Configure Jackson for Serialisation / Deserialisation
Start the application and call the first endpoint, **localhost:8080/movies**. The following error is produced as the objects cannot be serialised.

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

The solution to that is to use **Jackson annotations** and in specific *JsonProperty* annotation.

```java
public record Director(@JsonProperty("name")String name, @JsonProperty("surname")String surname) {
}

public record Movie(@JsonProperty("id") String id, @JsonProperty("director") Director director, @JsonProperty("released") Boolean released) {
}
```

Restart the application and you will be able to retrieve the list of movies.

The *getMovie* endpoint (**localhost:8080/movies/1**) uses the **id()** method which is provided by the Record. That way we can compare the id of each movie with the id provided in the api call.

Finally the *addMovie* endpoint is just a mock endpoint but it proves that deserialisation is also working. To test that you need to do a POST request to **localhost:8080/movies** with a valid body.

```javascript
{
	"id": "10",
	"director": {
		"name": "Jim",
		"surname": "White"
	},
	"released": false
}
```

## Conclusion
What could Records mean for the future of java? Maybe they are not meant to be used that way, but in general that feature is something that a lot of java developers anticipated. The fact that Records are immutable is adding a massive value. All the above in combination with the reduction of the boilerplate code make Records a promising feature.