Search⌘ K
AI Features

Records (Modern Java Feature)

Explore Java Records as a modern feature to create immutable data carriers with minimal code. Understand their syntax, immutability rules, accessor methods, and when to use them versus traditional classes. Discover how Records improve code clarity and help model simple data efficiently in Java 16 and later.

For many years, defining simple data carrier classes in Java required repetitive boilerplate. Modeling a database row, coordinate, or transaction typically requires defining private fields, constructors, getters, and explicit implementations of equals(), hashCode(), and toString(). Omitting these methods can lead to incorrect log output and inconsistent behavior in collections such as HashMap.

In this lesson, we will explore Records, a feature previewed in Java 14 but officially introduced in Java 16. Records allow us to declare a complete, immutable data carrier in a single line of code, letting the compiler handle the boring work while we focus on the data itself.

The boilerplate problem

To evaluate the benefits of records, consider the problem they address. In traditional Java, defining a class that holds an ID, description, and amount requires substantial boilerplate. These are referred to as boilerplate classes because the code is standardized, repetitive, and verbose.

Java 25
import java.util.Objects;
public class Transaction {
private final long id;
private final String description;
private final double amount;
// Constructor
public Transaction(long id, String description, double amount) {
this.id = id;
this.description = description;
this.amount = amount;
}
// Getters
public long getId() {
return id;
}
public String getDescription() {
return description;
}
public double getAmount() {
return amount;
}
// toString for readable output
@Override
public String toString() {
return "Transaction{id=" + id + ", description='" + description + "', amount=" + amount + "}";
}
// equals and hashCode for comparison and collections
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Transaction that = (Transaction) o;
return id == that.id &&
Double.compare(that.amount, amount) == 0 &&
Objects.equals(description, that.description);
}
@Override
public int hashCode() {
return Objects.hash(id, description, amount);
}
}
  • Lines 4–6: We declare private final fields to ensure the data cannot change after creation.

  • Lines 9–13: We write a constructor to assign values to these fields.

  • Lines 16–26: We write getter methods to expose the values.

  • Lines 29–48: We implement toString, equals, and hashCode so the object works correctly in lists and maps.

This is over 40 ...