Java : Record
In this article, we will explore the fundamental concepts of records in Java, understand their purpose and properties, and look at practical examples of how to create and use them effectively.
What are Records?
In Java, a record is a special kind of class that aims to reduce the boilerplate code required for implementing data-carrying classes. They automatically generate methods such as equals(), hashCode(), and toString()along with accessor methods for the fields. They were introduced in JDK 14 as a preview feature and became a standard feature in JDK 16.
Declaring & Instantiating
public record Employee(String id, String name, String designation) {}In the above example, The Employee record has 3 fields — id, name and designation. The Java compiler automatically generates the following
- A constructor accepting arguments for each field.
- Accessor methods for each field —
id(),name(), anddesignation(). equals(),hashCode()andtoString()methods.
A record can be instantiated just like a regular class, For example
Employee developer = new Employee("1", "John", "SDE-II"); Constructors
The automatically defined constructor that sets all the instance fields of a record is called the Canonical Constructor. If additional operations need to be performed before initializing the fields, you can provide a custom implementation. For example
public record Point(int x, int y) {
public Point(int x, int y) {
this.x = x < 0 ? -x : x;
this.y = y < 0 ? -y : y;
}
}For simple validation or normalization of parameter values in canonical constructor, a Compact Constructor can be defined. For example
// Validation of parameters
public record Point(int x, int y) {
public Point {
if (x < 0 || y < 0) throw new IllegalArgumentException();
}
}Unlike custom constructor, The compact constructor can only modify the constructor parameters before they are assigned to the instance fields. You cannot directly read or modify the instance fields in the body of the constructor.
// Normalizing parameters
public record Point(int x, int y) {
public Point {
x = x < 0 ? -x : x;
y = y < 0 ? -y : y;
}
}Fields & Methods
Instance fields of a record are automatically private and final, hence the instance fields are immutable. Instance fields of a record are called Components.
Get Guhan B’s stories in your inbox
Join Medium for free to get updates from this writer.
Accessor methods are automatically defined by the java compiler for the instance fields and every record has 3 methods defined automatically — equals(), hashCode() and toString().
You can define your own version of the automatically provided methods as long as they have the same parameters and return type.
Apart from the automatically generated methods, You can also define your own methods on a record. For Example
public record Point(int x, int y) {
public double distanceFrom(Point p) {
return Math.sqrt(Math.pow(this.x - p.x(), 2) + Math.pow(this.y - p.y(), 2));
}
}Record can also have static fields and methods, just like any class. For example
public record Point(int x, int y) {
public static Point ORIGIN = new Point(0, 0);
public static double distance(Point a, Point b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
}Records & Inheritance
A record cannot explicitly extend another record or any other class. This is because records are implicitly final, meaning they cannot be subclassed. Every record implicitly extends java.lang.Record.
Although records cannot explicitly extend another record or any other class, But they can implement any interface. For example
public record Point(int x, int y) implements Comparable<Point> {
public static Point ORIGIN = new Point(0, 0);
public double distance(Point a, Point b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
@Override
public int compareTo(Point other) {
double d_1 = Point.distance(this, Point.ORIGIN);
double d_2 = Point.distance(other, Point.ORIGIN);
return Double.compare(d_1, d_2);
}
}Record Patterns
Record patterns were introduced in JDK 21 (Preview Feature) as an elegant way to deconstruct records while performing pattern matching. They make it easier to match values against a record type and automatically bind record components to local variables.
A record pattern can also include an optional identifier, creating a Named Record Pattern. This allows you to still reference the entire record instance while also working with its components.
Using Record Patterns in instanceof
Type checking with instanceof can be combined with destructing while using records. Optionally Name Record Pattern can be used to reference the casted record object. For example
if (o instanceof Point(int x, int y) p) {
// ...
}Nested records destructing can also be achieved in the same fashion. For example
public record Dimension(int length, int width, int height) { }
public record Container(String name, Dimension dimension) { }
if (o instanceof Container(String name, Dimension(int length, int width, int height))) {
// ...
}Using Record Patterns in Switch Expressions
Record patterns integrate naturally with switch expressions. This allows concise and expressive branching based on record contents.
public static double area(Shape shape) {
return switch (shape) {
case Circle(double radius) -> //... ;
case Rectangle(double width, double height) -> //... ;
case Square(double size) -> //... ;
default -> // ... ;
}
}Record Patterns reduces boilerplate by combining type check + casting + unpacking. They also improve readability in instanceof and switch expressions.
Conclusion
In this article, We explored Records in Java —They provide a concise and powerful way to represent immutable data in Java.
By automatically generating constructors, accessors, and utility methods, they significantly reduce boilerplate code while improving readability.
Although records cannot extend classes, they can implement interfaces, making them a flexible choice for modeling simple, data-centric structures. With features like record patterns in newer JDKs, records are becoming even more expressive and practical in modern Java.










