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) {
}
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() {
...
}
}
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'])
}
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) {
}
Movie
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).
@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;
}
}
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) {
}
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
}
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.
Top comments (6)
Thank you for this introduction !
I'm able to build the project by setting the IDE as you tell, but I can't get the projet running. Whenever I start "bootRun", I get the following exception :
LinkageError com.psideris.moviesapi.MoviesApiApplication
java.lang.UnsupportedClassVersionError: Preview features are not enabled for com/psideris/moviesapi/MoviesApiApplication (class file version 58.65535). Try running with '--enable-preview'
The --enable-preview is set for compile and test in build.gradle. Is there something else to do to run the project with this param ? I can't figure out how.
Would be so much better if you didn't need the Jackson annotations (and it worked automatically like with normal classes). A lot of duplication / boilerplate
That would be nice indeed. For me it seems a fair trade, having to add annotations instead of declaring a whole class however the most significant benefit it's not the amount of code added or reduced but the immutability the Records offer.
It seems like something the devs will eventually get working without annotations, as records are so new (still a preview) . Indeed, the immutability is a great bonus / pattern.
Ok I get it finally. Need to add
So with older Java I'd use Lombok to eliminate the boiler plate code. I do like the option of @AllArgsConstructor and @NoArgsConstructor which is missing from this Record class.