Skip to main content

12.4 Records and Sealed Classes

Java 14 (preview) → 16 (stable) introduced Records, and Java 17 introduced Sealed Classes. Both are core features of modern Java that make code more concise and safer.

1. Records

When creating a simple class to hold data, you used to write all of the following manually:

  • private final field declarations
  • Constructor
  • getter methods
  • toString(), equals(), hashCode() overrides

A record generates all of this from a single line!

// Old approach: data class (very verbose)
public class PersonOld {
private final String name;
private final int age;

public PersonOld(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }

@Override
public String toString() {
return "PersonOld[name=" + name + ", age=" + age + "]";
}
// equals(), hashCode() omitted...
}

// Record approach: just 1 line!
public record Person(String name, int age) {}
public class RecordExample {
public static void main(String[] args) {
Person p = new Person("Alice", 25);

// Accessors are the field name (no "get" prefix)
System.out.println(p.name()); // Alice
System.out.println(p.age()); // 25

// toString, equals, hashCode provided automatically
System.out.println(p); // Person[name=Alice, age=25]

Person p2 = new Person("Alice", 25);
System.out.println(p.equals(p2)); // true (value comparison)
}
}

Records are immutable data objects — there are no setters, and fields cannot be changed after construction. They are ideal for API response data, DTOs (Data Transfer Objects), and value objects.

Compact Constructors

Records support compact constructors for adding validation:

public record Range(int min, int max) {
// Compact constructor — no parameter list, fields assigned automatically
Range {
if (min > max) {
throw new IllegalArgumentException("min must be <= max");
}
}
}

Range valid = new Range(1, 10); // OK
Range invalid = new Range(10, 1); // throws IllegalArgumentException

2. Sealed Classes

A sealed class explicitly restricts which classes may extend it. Use the sealed keyword with a permits clause.

// Shape can only be extended by Circle, Rectangle, and Triangle!
public sealed class Shape
permits Circle, Rectangle, Triangle {

public abstract double area();
}

// Each subclass must be declared final, sealed, or non-sealed
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }

@Override
public double area() { return Math.PI * radius * radius; }
}

public final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}

@Override
public double area() { return width * height; }
}

Sealed classes are especially powerful when combined with Java 21 pattern matching:

// Java 21 - switch pattern matching
static String describe(Shape shape) {
return switch (shape) {
case Circle c -> "Circle, area: " + c.area();
case Rectangle r -> "Rectangle, area: " + r.area();
default -> "Unknown shape";
};
}

Records and sealed classes are key modern Java features that make the language more expressive, safe, and easy to maintain.