Records
Define immutable data-carrier classes concisely with the record keyword (Java 16+).
Overview
Key Points
- •record Name(Type field1, Type field2) { } declares a record
- •Fields (components) are automatically final and private
- •Accessors are generated: field() method for each component (not getField())
- •equals() and hashCode() are based on all components
- •toString() prints ClassName[field1=value1, field2=value2]
- •You can add custom methods, static methods, and static fields
- •You can define a compact constructor to validate or normalize inputs
- •Records cannot extend other classes (implicitly extend java.lang.Record)
- •Records can implement interfaces
Examples
Basic record usage
public class Main {
record Point(int x, int y) {}
record Person(String name, int age) {
// Compact constructor: no parameter list, runs before field assignment
Person {
if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
name = name.trim(); // normalize; assigned to field automatically
}
// Custom method
String greeting() {
return "Hello, I am " + name + " and I am " + age + " years old.";
}
}
public static void main(String[] args) {
var p = new Point(3, 4);
System.out.println(p); // Point[x=3, y=4]
System.out.println(p.x()); // 3 (accessor, not getX())
System.out.println(p.y()); // 4
var p2 = new Point(3, 4);
System.out.println(p.equals(p2)); // true (value-based equality)
var alice = new Person(" Alice ", 30);
System.out.println(alice); // Person[name=Alice, age=30]
System.out.println(alice.greeting());
}
}The compact constructor does not list parameters; they are in scope automatically. Modifications to parameters (like name.trim()) are applied to the fields before they are set.
Records as DTOs and in collections
import java.util.*;
import java.util.stream.*;
public class Main {
record Product(String name, String category, double price) {}
public static void main(String[] args) {
var products = List.of(
new Product("Apple", "Fruit", 1.20),
new Product("Banana", "Fruit", 0.50),
new Product("Carrot", "Vegetable", 0.80),
new Product("Daikon", "Vegetable", 1.50)
);
// Records work well in streams
var byCategory = products.stream()
.collect(Collectors.groupingBy(Product::category));
byCategory.forEach((cat, ps) -> {
double avg = ps.stream().mapToDouble(Product::price).average().orElse(0);
System.out.printf("%s: avg price $%.2f%n", cat, avg);
});
// Sort by price
products.stream()
.sorted(Comparator.comparingDouble(Product::price))
.forEach(System.out::println);
}
}Product::name, Product::price are method references to the auto-generated accessors. Records integrate naturally with streams, collectors, and comparators.
Records implementing interfaces and with static members
public class Main {
interface Describable { String describe(); }
record Circle(double radius) implements Describable {
static final double PI = Math.PI;
double area() { return PI * radius * radius; }
double circumference() { return 2 * PI * radius; }
@Override
public String describe() {
return String.format("Circle(r=%.2f, area=%.2f)", radius, area());
}
}
record Rectangle(double width, double height) implements Describable {
double area() { return width * height; }
@Override
public String describe() {
return String.format("Rectangle(%sx%s, area=%.2f)", width, height, area());
}
}
public static void main(String[] args) {
Describable[] shapes = {
new Circle(5.0),
new Rectangle(3.0, 4.0),
new Circle(2.5)
};
for (var s : shapes) {
System.out.println(s.describe());
}
}
}Records can implement interfaces and have instance methods and static members. They just cannot extend other classes or have mutable fields.
Common Mistakes
- Using getX() style accessors. Record accessors are x(), not getX(). This is a deliberate design decision to distinguish records from regular JavaBeans
- Trying to modify a record field after construction. All fields are final. Create a new record with the changed values instead
- Forgetting that records are not replacements for all classes. Records are for immutable data. If you need mutable state, a regular class is still appropriate
Note
Use records for any simple data container: coordinates, configuration, API responses, event objects, and arguments to methods. They eliminate ~80% of the boilerplate code in typical Java data classes.
Exercises
Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.
Completed: 0 / 4 exercises
Knowledge Check
Question 1 of 4