Records, Sealed Classes & Enums
The modern Java type system: records, sealed hierarchies, pattern matching, and enums.
On this page
Modern Java (16+) added concise tools that remove boilerplate and make your types safer and clearer: records, sealed classes, and enums. They help you say more with less code.
Enums: a fixed set of named values
An enum defines a type with a fixed set of possible values. Instead of using loose strings or magic numbers, you get a small, safe, self-documenting set.
A car's gearstick has a fixed set of positions: Park, Reverse, Neutral, Drive. You can't shift into "Banana". An enum is that gearstick - it constrains a value to a known, valid set, so invalid states are impossible.
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}Day today = Day.WEDNESDAY;
String plan = switch (today) {
case SATURDAY, SUNDAY -> "Relax";
default -> "Work";
};
System.out.println(plan); // WorkEnums can also carry data and methods:
enum Planet {
EARTH(9.81), MARS(3.71);
private final double gravity;
Planet(double gravity) { this.gravity = gravity; }
double weight(double mass) { return mass * gravity; }
}Prefer enums over string constants
Using "WEDNESDAY" as a raw string invites typos ("WENSDAY") that compile fine
but fail at runtime. An enum catches such mistakes at compile time and enables
exhaustive switch handling. Reach for enums whenever a value has a fixed set of
options.
Records: data classes without the boilerplate
Before records, a simple data holder required a constructor, getters,
equals(), hashCode(), and toString() - dozens of lines for something
trivial. A record generates all of that automatically.
record Point(int x, int y) { }That single line gives you:
- a constructor
Point(int x, int y) - accessor methods
x()andy() - sensible
equals(),hashCode(), andtoString() - immutability - a record's fields can't change after creation
Point p = new Point(3, 4);
System.out.println(p.x()); // 3
System.out.println(p); // Point[x=3, y=4]
Point q = new Point(3, 4);
System.out.println(p.equals(q)); // true - value equality for free!A record is like a filled-out form: name here, address there - a fixed set of fields, filled once, then read many times. It's about carrying data, not behaving. When your class is really just data, make it a record.
Sealed classes: controlling who can extend
A sealed class or interface restricts which classes may extend or implement it. You explicitly list the permitted subtypes - no surprises.
sealed interface Shape permits Circle, Rectangle { }
record Circle(double radius) implements Shape { }
record Rectangle(double width, double height) implements Shape { }Now Shape has exactly two implementations, and Java knows it. This pairs
beautifully with switch pattern matching, which can be exhaustive - the
compiler guarantees you've handled every case:
double area = switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
// no 'default' needed - the compiler knows these are the only options
};Why 'sealing' is powerful
Sealed types let you say "this is a closed set of possibilities." Combined with records and pattern matching, they make modeling data - like the results of an operation, or the shapes in a drawing app - remarkably safe and expressive. Add a new subtype, and the compiler flags every switch you forgot to update.
Putting it together
sealed interface Payment permits Cash, Card { }
record Cash(double amount) implements Payment { }
record Card(double amount, String number) implements Payment { }
String describe(Payment p) {
return switch (p) {
case Cash c -> "Cash: $" + c.amount();
case Card c -> "Card ending " + c.number();
};
}Concise, immutable, type-safe, and exhaustive - modern Java at its best.
Quick check
What does declaring 'record Point(int x, int y) {}' give you automatically?
Key takeaways
- Enums define a fixed, type-safe set of named values and can carry data and methods.
- Prefer enums over raw string or int constants - mistakes are caught at compile time.
- Records are concise, immutable data classes that auto-generate constructor, accessors, equals, hashCode, and toString.
- Sealed classes/interfaces restrict which types may extend them via a 'permits' list.
- Sealed types plus record patterns enable exhaustive switch - the compiler ensures every case is handled.
One more essential OOP topic remains: how objects are compared for equality, and why immutability makes everything simpler. That's next.