Java Records : Boilerplate Exterminator

Reading Time: 6 minutes

Java records are a preview feature available in Java 14 which natively reduces boilerplate code when dealing with Data Classes. Learn how we can actually model data as data-only aggregates and close the gap in Java’s type system.

Java Records – What’s the Problem?

We all model data Classes like domain objects, DTO’s etc .. and we do it by encapsulating it in a Class.  We don’t really have any other option since that is the Java way. The problem is that we encounter so much overhead like …

  • Overriding equals ( ), toString ( ), hashCode ( )
  • Accessor methods
  • Constructor
  • Possibly enforcing immutability (final)

Most of us resort to third party frameworks like Lombok which allow us to use the @Data annotation to auto-generate the boilerplate code for us. Sometimes we actually write the code ourselves or have the IDE do it for us.  Either way, we do this this because Java just doesn’t have a native way to generate it for us.

Here is an example of a Java data class named “Location” which is just a data container for a geographic point (latitude and longitude) on the earth .

That is a lot of noise … all we want is to model Location as a type which groups related fields together as a data item. Debugging can get tricky with this boilerplate code since refactoring can lead to  hard to find bugs. There’s also the case where one may not even implement the code above at all .. of course I’m not talking about you 😉

Isn’t there a more concise way to model data in Java? There is now  and that’s where Java records come in.

What is a Java Record?

java 14 records image - MVP Java
java 14 records image

A Java Record is a simply a data container for a group of related fields you want to aggregate together. We now have a native way to achieve this. Here is an example of using a Java Record to replace the Location Class which was shown earlier …

Where did all that code go? Well, I guess I have some explaining to do now.  A record is “the state, the whole state, and nothing but the state” or put  another way “the fields, just the fields, and nothing but the fields“.

This means that a Java record allows us to make a stronger semantic statement in which the type (Location) is just the state provided (latitude, longitude) and the instance (india) is just an aggregate of the field values (28.6139, 77.2090).

In order to support this, we have a new java.lang.Record Class which extends Object and declares equals( ) , toString( ) and hashCode( ) as abstract. Don’t try to extend the Record class and instantiate it yourself, that won’t work since it is marked as final. You can only get one as I declared above, then javac will create it for you … but with some goodies.

You can check out the Video version of this article on YouTube …

What is the Advantage of a Java Record ?

The beauty is that all that ceremony code is auto-generated for you.  Here’s what is auto-generated ..

  • A default public constructor which assigns your state to final  instance variables
  • equals( ) , toString( ) and hashCode( ) method
  • accessor methods but no setters since records are by default immutable (fields are final)

A Java record declares its representation, and commits to an API that matches that representation. This means the field names become your API, thus you lose the flexibility to decouple API from representation. What does that mean?

It means you lose the flexibility to hide the representation behind names like getLat(), getX() or getWhateverYouCallit() etc ..

By default,  .latitude() and .longitude() are the auto-generated accessor methods you get, therefore the names you select for your state description are very important since they become your API (what you interact with)!

Here’s an example that will drive it home using jshell (all code on GitHub here). Notice

And the following output will show that the equals( ) , toString( ) , hashCode( ) and accessor methods have been auto-generated.

 Can I Customize a Java Record?

Yes, you can customize a Java record. You may want to add validation in the constructor or add some static factory method. You can customize a Java record to the point that it largely resembles an actual Java Class which begs the question, should you?

Beside a Java Record not being able to extend a Class or being made abstract there is the limitation where you can only add additional fields which are static.

The following example introduces parameter validation in the canonical constructor (the one whose signature matches the record’s state description). Even if you leave out the args and variable assignments in the canonical constructor like I have below, javac will still do it for you. There is also a custom toString() and I have even brought back our good old getters.

Here is the output when the above is executed in JShell.

 

My advise is to not customize too much like the example above. If you need to perform validation and/or add some static factory method than that’s ok but once you open the flood gates, it’s better to go with a standard Java Class.

Summary

Java records provide first class support for modeling data only aggregates and eliminates much of the boilerplate code in doing so but that’s not all. Aside from this, it  provides a clearer way for APIs, compilers and frameworks to understand and process the high level data. It will be interesting to see what developments will be made in that area in the near future.

Will this close a possible gap in Java’s type system? Time will tell but I already like what I see and seeing as this is an experimental preview feature introduced in Java 14, your feedback is welcome here at the Amber mailing list.

Check out another preview feature article here  (Switch expression).

All code on MVP Java’s GitHub account