Recent Java releases have quietly transformed the language. Records, sealed interfaces, and pattern matching combine to make modeling data far less verbose than the old getter-and-setter era.
Records replace boilerplate
A record is an immutable data carrier. The compiler generates the constructor,
accessors, equals, hashCode, and toString for you.
public record Point(int x, int y) {
// Compact constructor for validation
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("coordinates must be non-negative");
}
}
}Sealed types model a closed set
A sealed interface restricts which types can implement it, giving you an exhaustive, closed hierarchy.
public sealed interface Shape permits Circle, Rectangle {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}Pattern matching for switch
Combine sealed types with pattern matching and the compiler enforces that you handle every case — no default branch needed.
static double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
};
}Record deconstruction
Newer Java lets you destructure records directly in patterns, pulling out fields in one step.
static String describe(Shape shape) {
return switch (shape) {
case Circle(double r) -> "circle radius " + r;
case Rectangle(double w, double h) -> "rect " + w + "x" + h;
};
}Why it matters
Together these features let the compiler catch missing cases at build time and
cut the noise around data modeling. Reach for them whenever you'd otherwise
write a verbose class or a fragile instanceof chain.