Java’s Records vs Kotlin’s Data Classes

Janani Subbiah
Javarevisited
Published in
4 min readDec 31, 2020

--

How record classes in Java compares to Kotlin’s data classes.

Photo by Mike Kenneally on Unsplash

Recent releases of Java have had some very noteworthy and developer-friendly features including a concept of record classes. From the Java docs of the Record class:

A record class is a shallowly immutable, transparent carrier for a fixed set of values, called the record components. The Java language provides concise syntax for declaring record classes, whereby the record components are declared in the record header. The list of record components declared in the record header form the record descriptor.

My first thought on seeing this class and as someone who has used Kotlin for some time now, I couldn’t help but wonder how similar this sounds to Kotlin’s data classes.

So I did some exploring and here is my first impression comparing Java’s records to Kotlin’s data classes. But before that, let us take a quick look at what these are and how we can create one:

Records

public record Sample (String key, String value) {}

A record is a data holder that extends from Java’s base Record class. A key feature of records classes are

  • All properties are final and private.
  • Declaring a class using the record key word generates the equals(), hashcode() and toString() methods hence reducing boiler plate code.

Data class

data class Sample (val key, val value)

Data classes are Kotlin’s data holder classes and some of its key features are:

  • Properties can be final or not (declaring properties as a var makes them not final and hence they can be changed).
  • Similar to record classes, declaring a class as a data class automatically generates the equals(), hashcode() and toString() methods.

With the basic definitions out of the way, let's talk about some key similarities and differences:

Similarities

Well, the first one that comes to mind is obviously how both these features remove the need for boilerplate code by automatically generating three methods: equals(), hashcode() and toString(). Under the hood, how this code gets generated is different but from a high level to a developer, the code is automatically generated.

Differences

1: Data classes provide an additional copy() method out of the box that allows you to selectively change property values of a class by just specifying a new value for that field, without having to copy over all the other values that don’t need a new value.

For example, let us create an object of the Sample data class:

var sample1 = Sample("Key-1", "Value-1")

If we were to create another Sample object similar to sample1 but only change the value of the key field we can do it as follows:

val sample2 = sample1.copy(key = "Key-2")

This feature truly shines when you have multiple fields in your data class and you just have to change one of them. No such feature is available with Records.

2: Fields in a record class are ALL private and final, but with data classes, this is not always the case as it depends on how the fields are declared. For example, if we were to update the Sample class to be as follows:

data class Sample(var key, var value)

We can change the values of the fields key and value as they are now a var vs a val.

3: There is no base data class instead the word data is just a key word to indicate what type of class is being declared. With records, there is a base Record class that is provided in the JDK that all classes that are declared as record classes extend from. It is the base Record class that provides the hashCode(), toString() and equals() methods.

4: Data classes can extend other classes in Kotlin if those classes can be and are marked as open (making then inheritable). Note that all classes are final in Koltin by default unless they are marked as open. But Java’s record classes cannot extend other classes. For example, the following code will work in Kotlin:

open class Parent
data class Sample(var key, var value): Parent()

On the other hand, the following will not compile in Java (assuming the Parent class is

class Parent {}
public record SampleRecord(String key, String value) extends Parent// The above line will fail to compile with "No extends clause
// allowed for record"

5: The final and most significant difference (in my opinion) between Records and Data classes is the ability to define instance variables. In a data class we can do the following:

data class Sample(var key, var value) {    var option: String? = ""}

And when we create an object of type Sample we can choose to provide a value for the option field. On the other hand record classes do not allow instance variables. So the following code will not compile:

public record SampleRecord(String key, String value) {

String hello = ""; // This line will not compile.
}

Conclusion

I hope that was a good sum-up of how Java’s record classes compare against Kotlin’s data classes! Record classes are definitely more immutable as compared to data classes whose immutability depends on how the class is declared. But the similarity between the two is a big win for all developers: automatic generation of boilerplate code!

--

--

Janani Subbiah
Javarevisited

Product Architect | Ice cream lover | Newbie gardener