Java Records: Immutable Data Objects for Cleaner Code
Discover Java Records, introduced in Java 14 and enhanced in Java 15. This feature allows developers to create immutable data objects with ease. Learn how records simplify data handling in applications, making it easier to store and transfer data across different layers of your Java applications.
Java - Record
In Java 14, an exciting feature called record was introduced as a preview feature. The record feature helps in creating immutable data objects. In Java 15, record types were further enhanced. During Java 14 and 15, to use a record, the flag --enable-preview
had to be passed. However, from Java 16 onwards, this flag is no longer required, as records are a standard part of the JDK.
Purpose of a Java Record
The primary purpose of a record is to create a data object or a Plain Old Java Object (POJO) used to carry data in application program flow. In a multi-tier application, domain/model objects store data captured from the data source. These model objects are then passed to the application/UI layer for processing, and vice versa, where the UI/application stores data in data objects and passes these objects to the data layer to populate data sources.
Since these data objects can contain many fields, developers are often required to write numerous setter/getter methods, parameterized constructors, and overridden equals
and hashCode
methods. In such cases, records help by providing most of the boilerplate code, allowing developers to focus on essential functionalities.
Features of Java Record
The following features make records an exciting addition:
- Record objects have an implicit constructor with all parameters as field variables.
- Record objects include implicit field getter methods for each field variable.
- Record objects have implicit field setter methods for each field variable.
- Record objects feature an implicit, sensible implementation of
hashCode()
,equals()
, andtoString()
methods. - With Java 15, native methods cannot be declared in records.
- With Java 15, implicit fields of records are not final, and modification using reflection will throw an
IllegalAccessException
.
Example Without Using Java Record
Let's create a simple program without using records. We will create a Student
object and print its details. The Student
class has three properties: id
, name
, and className
. To create a student, we will define a parameterized constructor along with setter and getter methods, equals
, and hashCode
methods. This leads to our Student
class consisting of over 60 lines of code.
Java Code Example Without Using Record
package com.tutorialsarena;
public class Tester {
public static void main(String args[]) {
// create student objects
Student student1 = new Student(1, "John", "X");
Student student2 = new Student(2, "Doe", "X");
// print the students
System.out.println(student1);
System.out.println(student2);
// check if students are the same
boolean result = student1.equals(student2);
System.out.println(result);
// check if students are the same
result = student1.equals(student1);
System.out.println(result);
// get the hashcode
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
}
}
class Student {
private int id;
private String name;
private String className;
Student(int id, String name, String className) {
this.id = id;
this.name = name;
this.className = className;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
@Override
public String toString() {
return "Student[id: " + id + ", name: " + name
+ ", class: " + className + "]";
}
@Override
public boolean equals(Object obj) {
if(obj == null || !(obj instanceof Student)) {
return false;
}
Student s = (Student)obj;
return this.name.equals(s.name)
&& this.id == s.id
&& this.className.equals(s.className);
}
@Override
public int hashCode() {
int prime = 19;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((className == null) ? 0 : className.hashCode());
result = prime * result + id;
return result;
}
}
Output
Student[id: 1, name: John, class: X]
Student[id: 2, name: Doe, class: X]
false
true
123456789
987654321
Example Using Java Record
Now, let's recreate the above program using records. Here, we will create a Student
object as a record and print its details. You can see that the complete Student
class is reduced to just one line of code.
Java Code Example Using Record
package com.tutorialsarena;
public class Tester {
public static void main(String args[]) {
// create student objects
Student student1 = new Student(1, "John", "X");
Student student2 = new Student(2, "Doe", "X");
// print the students
System.out.println(student1);
System.out.println(student2);
// check if students are the same
boolean result = student1.equals(student2);
System.out.println(result);
// check if students are the same
result = student1.equals(student1);
System.out.println(result);
// get the hashcode
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
}
}
record Student(int id, String name, String className) {}
Output
Student[id: 1, name: John, class: X]
Student[id: 2, name: Doe, class: X]
false
true
123456789
987654321
We can also add custom methods in records, but it is generally not required.
Java Record for Sealed Interfaces
Records are final by default and can extend interfaces. We can define sealed interfaces and let records implement them for better code management.
Example: Use of Java Record for Sealed Interfaces
Consider the following example:
Sealed Interfaces Example
package com.tutorialsarena;
public class Tester {
public static void main(String[] args) {
Person employee = new Employee(23, "Robert");
System.out.println(employee.id());
System.out.println(employee.name());
}
}
sealed interface Person permits Employee, Manager {
int id();
String name();
}
record Employee(int id, String name) implements Person {}
record Manager(int id, String name) implements Person {}
Output
23
Robert
Overriding Methods of Java Records
We can easily override a record method implementation and provide our own version.
Example: Override Java Record Methods
Consider the following example:
Override Java Record Methods Example
package com.tutorialsarena;
public class Tester {
public static void main(String[] args) {
Student student = new Student(1, "John", "X");
System.out.println(student);
}
}
record Student(int id, String name, String className) {
@Override
public String toString() {
return "Student[id: " + id + ", name: " + name + ", class: " + className + "]";
}
}
Output
Student[id: 1, name: John, class: X]
Conclusion
Records offer a streamlined way to create immutable data objects in Java. With features such as implicit constructors, getter methods, and overridden methods for equals
, hashCode
, and toString
, records significantly reduce the boilerplate code developers must write. This allows developers to focus more on functionality rather than repetitive code.