Skip to content

8.2. Record Classes (Java 14+)

Record classes are a special kind of class that act as transparent carriers for immutable data. They automatically generate constructors, accessors, equals(), hashCode(), and toString() methods.

Basic Record Syntax

java
// Simple record declaration
record Point(int x, int y) { }

record Person(String name, int age, String email) { }

record Circle(double radius, String color) { }

Using Records

java
public class RecordDemo {
    public static void main(String[] args) {
        // Creating record instances
        Point p1 = new Point(10, 20);
        Point p2 = new Point(10, 20);
        Point p3 = new Point(30, 40);

        Person person = new Person("Alice", 30, "alice@example.com");

        // Automatic accessors (no get prefix)
        System.out.println("Point: (" + p1.x() + ", " + p1.y() + ")");
        System.out.println("Person: " + person.name() + ", " + person.age());

        // Automatic equals and hashCode
        System.out.println("p1 equals p2: " + p1.equals(p2));  // true
        System.out.println("p1 equals p3: " + p1.equals(p3));  // false

        // Automatic toString
        System.out.println(p1);  // Point[x=10, y=20]
        System.out.println(person);  // Person[name=Alice, age=30, email=alice@example.com]
    }
}

Customizing Records

java
record BankAccount(String accountNumber, String accountHolder, double balance) {
    // Compact constructor for validation
    public BankAccount {
        if (balance < 0) {
            throw new IllegalArgumentException("Balance cannot be negative");
        }
        if (accountNumber == null || accountNumber.isBlank()) {
            throw new IllegalArgumentException("Account number is required");
        }
    }

    // Additional methods
    public BankAccount deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Deposit amount must be positive");
        }
        return new BankAccount(accountNumber, accountHolder, balance + amount);
    }

    public BankAccount withdraw(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Withdrawal amount must be positive");
        }
        if (amount > balance) {
            throw new IllegalArgumentException("Insufficient funds");
        }
        return new BankAccount(accountNumber, accountHolder, balance - amount);
    }

    // Static methods
    public static BankAccount createEmpty(String accountNumber, String accountHolder) {
        return new BankAccount(accountNumber, accountHolder, 0.0);
    }
}

Record with Additional Fields

java
record Employee(String id, String name, double salary) {
    // Additional field (must be static)
    private static final String COMPANY = "Tech Corp";

    // Additional computed field (not part of state)
    public String email() {
        return name.toLowerCase().replace(" ", ".") + "@" + COMPANY.toLowerCase() + ".com";
    }

    public String displayInfo() {
        return String.format("%s (%s) - $%.2f - %s", name, id, salary, email());
    }
}

Records in Collections

java
import java.util.*;
import java.util.stream.Collectors;

record Student(String id, String name, int grade) implements Comparable<Student> {
    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.grade, other.grade);
    }
}

public class StudentRecords {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("S1", "Alice", 85),
            new Student("S2", "Bob", 92),
            new Student("S3", "Charlie", 78)
        );

        // Sort using natural ordering (by grade)
        students.sort(Comparator.naturalOrder());

        // Process records
        students.forEach(System.out::println);

        // Use in maps
        Map<String, Student> studentMap = students.stream()
            .collect(Collectors.toMap(Student::id, student -> student));
    }
}

Record Limitations

  1. Implicitly final - cannot be extended
  2. Components are implicitly final
  3. Cannot declare instance fields outside the header
  4. All components become private final fields