Records and Sealed Classes

Modern Java is moving away from the “everything is a complex object” philosophy toward Data-Oriented Programming. Two of the most important tools for this are Records and Sealed Classes.

1. Records (Data Carriers)

Before Java 14, creating a simple data carrier (POJO) required dozens of lines of boilerplate (Getters, equals, hashCode, toString).

  • A Record is an immutable data carrier that generates all of this for you in one line.
public record User(String name, String email, int age) {}

Key Features of Records:

  • Immutable: All fields are final by default.
  • Concise: No more boilerplate.
  • Canonical Constructor: Automatically handles assignment.
  • Validation: You can add validation logic in a “Compact Constructor.”
public record User(String name, String email) {
    public User {
        if (name.isBlank()) throw new IllegalArgumentException("Name cannot be blank");
    }
}

2. Sealed Classes (Restricted Hierarchies)

Traditionally, a class could be extended by anything unless it was final. Sealed Classes give you a middle ground: you define exactly which classes are allowed to extend your class.

public sealed class Shape permits Circle, Square, Triangle {}

public final class Circle extends Shape { ... }
public final class Square extends Shape { ... }
public final class Triangle extends Shape { ... }

Why use Sealed Classes?

  • Security: Prevents unauthorized subclasses.
  • Exhaustiveness: When using switch expressions (covered in the next chapter), the compiler knows exactly all possible types, so you don’t need a default case.

3. Interactive: Boilerplate Shredder

See how much code you save using Records.

Legacy Class (Java 8)
public class User {   private final String name;   public User(String n) {this.name=n;}   public String getName() {return name;}   @Override public boolean equals(Object o)...   @Override public int hashCode()...   @Override public String toString()... }
➡️
Modern Record (Java 17+)
record User(String name) {}

4. Best Practices

  • DTOs: Use Records for Data Transfer Objects (fetching data from DB or API).
  • Domain Logic: Use Sealed Classes for State Machines or Command patterns.
  • Immutability: Remember that while a Record is final, its fields (like a List) may still be mutable elements themselves. Always use List.of() or Collections.unmodifiableList().