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.
Note: Java 17 is about to be released (September 2021) which is the first LTS version with records support.
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 public constructor (with all arguments), read methods for each field (equivalent to getters) and the implementation of hashCode, equals and toString methods.
This is how a Record and Class, for the same data structure, would look like.
Record
public record Person(String name, int age) {
}
Enter fullscreen mode Exit fullscreen mode
Class
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() {
...
}
}
Enter fullscreen mode Exit fullscreen mode
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:
psideris89 / java-records-demo
Demo spring boot rest api for java 14 records demonstration
Step 1: Create Spring Boot app
You can use spring initialiser (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.
Step 2: Project configuration
If you are using an IDE you need to use jdk 16. For Intellij set the project sdk to 16 and the project language level to 16.
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.
compileJava {
options.compilerArgs += ["--enable-preview"]
}
test {
jvmArgs(['--enable-preview'])
}
Enter fullscreen mode Exit fullscreen mode
Step 3: Create Rest Controller
Before we create the rest controller we need to define the Records.
Director
public record Director(String name, String surname) {
}
Enter fullscreen mode Exit fullscreen mode
Movie
public record Movie(String id, Director director, Boolean released) {
}
Enter fullscreen mode Exit fullscreen mode
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).
@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;
}
}
Enter fullscreen mode Exit fullscreen mode
Step 4: 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.
The solution to that is to use Jackson annotations and in specific JsonProperty annotation.
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) {
}
Enter fullscreen mode Exit fullscreen mode
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.
{
"id": "10",
"director": {
"name": "Jim",
"surname": "White"
},
"released": false
}
Enter fullscreen mode Exit fullscreen mode
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.
暂无评论内容