A Record is a special form of class in Java like the enums. It was released in Java 16, after being introduced as a preview feature in Java 14.
Records provides a way to create data-only classes with minimal syntax, avoiding the need to write a lot of boilerplate code.
For example, prior to Records, here’s how one would define a data-only classes (POJO) to represent a Point in the x-y plane.
class Point {
private double x;
private double y;
public Point(double x, double y) {
// initialize
}
public double getX() {
return x;
}
public double getY(){
return y;
}
public boolean equals(Object obj) {
// Tests whether or not this object is equal to the specified object.
}
public int hashCode() {
// Calculate and return hashcode
}
public String toString() {
// Return string representation of this object.
}
}
This class is a pain to write. Even if you argue you can get the IDE to auto-generate most of it or use Lombok, it’s still a pain to read. In fact, it doesn’t need to be read at all.
You can represent the Point
class (above) as a record with much less ceremony:
record Point(double x, double y) { }
That’s it. With just one line of code, we expressed the same verbose POJO class. Clean!
A record:
- consists of a name. In this case,
Point
. - has a list of record components. In this case,
double x
anddouble y
.
What You Get with Records
Declaring Point
as record
does the following:
- declares a
final
class (cannot be extended, cannot be abstract) - declares ‘immutable’ instances variables from the record components. For
Point
, it will declarex
andy
asprivate final
, accessible usingp.x()
andp.y()
, notp.getX()
orp.getY()
. Note: The immutability is shallow; if the instance fields are objects, their state is not regulated and could be changed. - provides a canonical constructor which sets all the instance fields.
- provides
equals()
,hashCode()
andtoString()
methods. - Serialization, provided you implement the
Serializable
e.g.record Point(double x, double y) implements Serializable {}
Syntactic Sugar over Flexibility
At the start of this article, I mentioned that records are a special form of class. This is true and to further refine the definition, you can think of them as a restricted kind of class like the enums. In both cases, we give up some flexibility and in return get syntactic sugar or semantic benefits. As developers, we can choose whether we want the flexibility given the use case or syntactic benefits.
Breaking Encapsulation
By coupling or binding the API to the representation e.g. p.x()
, we expose the internal details, hence breaking encapsulation, a core concept of object-oriented design. But sometimes, for simple cases like the Point
class that we defined, encapsulation is an overkill and there’s no need for it, especially when the downside is boilerplate code.
Another argument for p.x()
over p.getX()
is that the relationship x
and p.x()
is a compiler level commitment. Whereas x
and p.getX()
is only by convention.
Constructors
Records provide a canonical constructor out of the box. However, you can customize the constructor. This is often done to validate arguments. For example.
public record WholeNumber(int n) {
public WholeNumber(int n) {
if (n < 0)
throw new IllegalArgumentException(String.format("%d", n));
this.n = n;
}
}
This is a common use case and thankfully, Java implementors allowed a more compact and concise form where the arguments to the constructor can be omitted as well as instance field assignment. This is called the compact constructor. The code for initializing instance fields is supplied by the compiler.
public record WholeNumber(int n) {
public WholeNumber {
if (n < 0)
throw new IllegalArgumentException(String.format("%d", n));
}
}
Instance Methods
Records can have instance methods. In addition, we can also override toString()
, equals()
etc. methods
public record Integer(int n) {
public boolean isPositive() {
return n > 0;
}
}
Static Fields and Methods
Records can have static fields and static methods. But only static
fields are allowed. Records cannot declare non-static instance fields. The only fields a record will have are those that are defined in the record component. The reason behind this is so that the state of a record can be entirely determined using record components.
public record Integer(int n) {
public static Integer MAX = new Integer(Integer.MAX_VALUE);
}
Generics
Generics are allowed too.
public record Comparision<T>(T first, T second) {}
Inheritance
Because the intention of Records is to model data-only classes, inheritance is not supported: Records cannot extend any other class.
APIs Related to Records
boolean isRecord()
: Returns true if the class was declared as a record.RecordComponent[] getRecordComponents()
: Returns an array of record components.
Summary
Records help us reduce boilerplate code by providing a concise way to model data rather than using traditional classes. They make domain classes more concise and easier to read. Some of the use cases such as returning multiple values from methods, compound keys (e.g. new HashMap<Point, String>
where Point
is a record,) DTOs and more. Records are certainly useful and will be more so with pattern matching, inline classes, primitive objects and more features in the future releases.
This article was written by Umer Mansoor
Comments (3)
GDF
Thank you for the information. I rarely make comments but this one taught me everything I know about records in less than 5 minutes.
Umer M.
Glad to hear that. It’s a simple yet powerful concept. Hope they wider adoption and help get rid of boilerplate, JavaBeans and tons of messy POJOs, DAOs required by frameworks like Hibernate.
Andrew
If you are using versions older than Java 16 on command line, you must enable records during compilation and runtime.
javac --enable-preview --release 14 Point.java
To execute:
java --enable-preview Point