Spring Boot and Java 16 Records
In this article, we will discuss Java 16's newest feature, Records. Then we will apply this knowledge and use it in conjunction with a Spring Boot application.
Join the DZone community and get the full member experience.Join For Free
On March 16th, 2021, Java 16 was GA. With this new release, tons of new exciting features have been added. Check out the release notes to know more about these changes in detail. This article's focus will be on Java Records, which got delivered with JEP 395. Records were first introduced in JDK 14 as a preview feature proposed by JEP 359, and with JDK 15, they remained in preview with JEP 384. However, with JDK 16, Records are no longer in preview.
I have picked Records because they are definitely the most favored feature added in Java 16, according to this Twitter poll by Java Champion Mala Gupta.
I also conducted a similar survey, but it was focused on features from Java 8 onwards. The results were not unexpected, as Java 8 is still widely used. Very unfortunate, though, as tons of new features and improvements are added to newer Java versions. But in terms of features, Java 8 was definitely a game-changer from a developer perspective.
So let's discuss what the fuss is about Java Records.
What are Records?
As per the JEP 395:
Records are a new kind of class in the Java language. They act as transparent carriers for immutable data with less ceremony than normal classes. Records can be thought of as nominal tuples.
Another quote from the JEP clearly explains developers' frustration while writing typical data carrier classes. Properly writing such a data-carrier class involves a lot of low-value, repetitive, error-prone code: constructors, accessors, equals, hashCode, toString, etc. For example, a class to carry x and y coordinates inevitably ends up like this:
Another option that we developers use most often is to leave the handling of the boilerplate to IDE. For example, with IntelliJ, you can generate constructors, getters, setters, equals, hashCode, and toString, etc., by simply pressing Command + N shortcut key on macOS. But the boilerplate code is still there.
With Java 16 Records, it's just one line of code. Cool, isn't it.
Here a record class declaration consists of a name, optional type parameters, a header, and a body.
Internals of the Java record class can be checked using a decompiler that comes with IntelliJ IDE or can use the
javap command-line utility. To understand the internals, we created the following record class.
Following is the decompiled Java record class. I have used the
javap command-line utility to check class file internals.
Following is the output.
You can conclude the following from the above output.
- State record class is extending the
- State record class is declared final cannot be extended further using extends keyword.
- hashCode(), equals(), toString() and a canonical constructor are implicitly generated for us.
- There are no setters or getters, only accessors.
- With no setters and final in the class declaration clarifies that you cannot change the state, and hence records are immutable.
You can further validate these points by writing tests as well.
There are some restrictions in the declaration of record classes compared to normal classes—check out JEP 395 for such restrictions.
Lombok and Records, Friends or Foe?.
You are probably already using Lombok annotations such as @Value, which is closest if not the same to the Java records. Then you can get rid of one dependency and those Christmas trees of annotations. I might have oversimplified things, and it may make sense to replace Lombok for some cases.
But you might be using Lombok for other features and not just one annotation that it provides. And believe me, while Java Records are a welcome feature for Java lovers, it's not going to replace Lombok, at least for now. You do not believe me? Check out this answer from Brain Goetz on StackOverflow.
And be careful with what dependencies you add to your project, as the problem that is part of that dependency becomes your problem too.
Spring Boot and Java Records
From version 2.5.0-M1 onwards, Spring Boot provides preliminary support for Java 16. I have a working Spring Boot application that I will use to demo Java Records with it. The source code is available here. This is a simple Spring Boot application which, when accessed via /states or /state?name=statename REST endpoint show all or specific Indian states and their capitals. This application uses an in-memory H2 database that inserts rows at the start of the application.
As always, you can use start.spring.io to generate stubs for your Spring Boot application, and as explained earlier, make sure that you select the 2.5.x milestone version.
Here is how the REST controller class looks like.
We can focus on the getAllStates() method, which returns a list of State record class objects.
We have already seen the State record class. There is no change in that.
Following is the
StateRepository interface implemented by the
StateService Class is autowired using the constructor of the
Controller class. It has a method named findAll() that uses Spring JdbcTemplate to query and returns a
State record class list from the in-memory H2 database. As you can see, we have used the
RowMapper functional interface, which JdbcTemplate uses for mapping rows of a ResultSet on a per-row basis, and it returns the Row object for the current row. We have also used the
new keyword to initialize the record class, which means we can initialize the record class like normal Java classes. I have also used the Java 15 Text Blocks feature, which helps in the readability of SQL queries and JSON string values.
However, there were some issues when I started using records with this application. Earlier I was using
BeanPropertyRowMapper, This resulted in the following exception when I disabled Lombok and used Records instead for the
From the exception and
BeanPropertyRowMapper documentation, it is pretty clear that we must declare a default or no-arg constructor in the
State records class, which leads to some interesting discoveries about the records classes in Java.
To solve this error, I naively added a no-arg constructor to the
State record class.
But, that resulted in the following compilation error.
Non-canonical record constructor must delegate to another constructor
To solve this compilation, I added the following constructor, but this will make the response's values.
Then I took the help of the IntelliJ feature to generate the constructor for this record class. It provided me with the following options.
I tried these options but got the same result. I already knew that these options wouldn't work, but I tried my luck, which makes me wonder how to use records with
BeanPropertyRowMapper. I don't have an answer for this right now, but I will dig this further. If you see any issue with the code or have a better answer, then let me know.
Update:- I posted these exceptions to Spring Boot Gitter chat and got this answer.
BeanPropertyRowMapper can't be used with records since it consists of creating an instance of a Java Bean with its no-arg constructor and then calling its setters to populate the bean. But records don't have a no-arg constructor and are immutable and thus don't have setters. So, either use a traditional Java Bean or use records, but then don't use BeanPropertyRowMapper.
Fair enough. So clearly
BeanPropertyRowMapper cannot be used with records.
It's a wrap for now. Happy coding.
In this article, you have learned that Records are immutable data carrier classes that reduce lots of boilerplate code that we are used to writing. Then we looked at the internals of a Record class and discovered that hashCode(), equals(), toString(), and constructors are implicitly generated for us by the compiler. Then we learned that you should not really compare or replace Records with external libraries like Lombok because both are different tools for different things.
In the last section, we discovered that Records are good for use in cases such as getting data from a database (or some external service) with an example of the Spring Boot application. We also discovered some issues while using
BeanPropertyRowMapper, and concluded that we could not use it with records.
Published at DZone with permission of Ashish Choudhary. See the original article here.
Opinions expressed by DZone contributors are their own.