Java 14 - First impression of Records

Markus Dybeck
PriceRunner Tech & Data
4 min readJun 15, 2020

Java 14 have been out for a few months now and we have started to integrate it in a few applications here at PriceRunner. As you all probably know Records are a preview feature in the Java 14 release and in this article I’ll share my first impressions and discussions that came up in my team when first implementing records in our production code.

Photo by Jack Hamilton on Unsplash

What is a Record?

Records are in many ways just an easy way to write regular java classes. A record will compile into an ordinary Java class that extends the Record super class which comes with a few differences. When declaring a record class, member accessors will be created for you, as well as the constructor and the equals(), hashCode() and toString() methods.

As we can see in the above example, declaring a record is simple and can be done with only one line of code. One big difference here is that the record is identified by its field members x and y and not by reference, in other words, two instances created with the same field values will resolve into equal objects.

Records in production

Despite the nice way of declaring a point in space with records, one might not have that many obvious use cases to justify the move into Java 14. But as stated in an article by Ben Evans:

A record is an immutable transparent carrier for a fixed set of values

Records are more or less just a state for a fixed set of values. Writing immutable domain classes are something that we do frequently here at PriceRunner, and most of these could probably be converted into records.

Records as data domains

Using records for your domain models are perfect in many ways, but they may also interfere with your typical way of writing classes. Declaring a model with just a few fields is a neat way of using records.

Above we are using the compact constructor in order to validate that the products identifier is positive, and the whole model is written with just five lines of code. Getting rid of all the boilerplate code makes you focus on your domain and its logic.

How about more complex models?
At PriceRunner we typically don’t use constructors with more than 2–3 arguments, instead we’re using the builder pattern, hence one argument constructor. This patterns is not possible when using records as the record is defined by its components (the fields/arguments).

As I see it, we have two options:

  1. Stop using the builder pattern
  2. Use regular classes

It is a good time to reflect on why we are using the builder pattern and it’s probably worth its own post, but the builder pattern comes with at least two nice pros.

  • A neat and readable constructor
  • A fluent way of instantiate your class.

One could argue that you do get a readable constructor with records, it might even be more readable if you’re using the compact constructor, but you do not get that fluent way to instantiate your class.

Time will tell where we draw the line. For now we have said that a record is great when you have small models, but if you have to write boilerplate code for your record, you are not that far away from a regular class. It could for instance be that our Product model should be identified by its ProductId, then we need to override the equals method and are stretching the definition of a record as a record is by design a value object that is identified by its values/fields.

Records as local data carriers

As mentioned earlier, records are carriers for a fixed set of values, which are handy when you need to transport data in a local scope. That could for instance be between methods where you need a logical way to group things, where one option for that today is inner classes.

Today there are common third party libraries that provides such easy to use data structures, like Pair or Tuple . With records, we can easily name our own and get a more readable and understandable code. So instead of pair.getLeft() we can for instance use nameAndDescription.name() if we had a method that just returned the name and the description from our previous product model.

Summary

Records are immutable data carriers which are handy when you have small immutable models that are identified by their fields, such as an ID. They are also useful when you need to transport data in a local scope and could be a good option for inner classes.

Records could also be used for more complex models, but that comes with a tradeoff, such as the lack of the builder pattern and that complex models usually aren’t identified by all its fields.

Markus Dybeck
Backend developer, PriceRunner

--

--