Java Records and Sealed Classes Explained
BeginnerRecords (Java 16) and sealed classes (Java 17) modernise how you model data in Java. A record is a compact, immutable data carrier: you declare the fields once and the compiler generates the constructor, accessors, equals, hashCode, and toString. A sealed class or interface restricts which types may extend it, giving you a closed set of subtypes. Together with pattern matching, they let you model a domain precisely and handle every case exhaustively.
Records are forms; sealed types are multiple-choice questions
A record is like a printed form with fixed fields — name, amount, date — that cannot be altered once filled in; anyone reading it knows exactly what it contains. A sealed type is like a multiple-choice question with a fixed list of answers: a Shape may be only a Circle, Square, or Triangle and nothing else. Because the options are closed, the compiler can check that your code handles every one.
Step by Step
Key Concepts
Record
An immutable, transparent carrier of data. The compiler derives the constructor, accessors, equals, hashCode, and toString from the component list, eliminating boilerplate.
Sealed Type
A class or interface that explicitly permits a fixed set of subtypes. This closes the hierarchy so tooling and the compiler know every possible implementation.
Pattern Matching
Language support (in instanceof and switch) that tests a type and binds its data in one step, and — with sealed types — checks exhaustiveness across all cases.
Immutability
Records cannot be modified after construction, making them inherently thread-safe and safe to share, cache, and use as map keys.
Key Facts
- Records are ideal for DTOs, value objects, and API request/response bodies — anywhere you carry data without behaviour.
- A record can implement interfaces and add methods, but it cannot extend another class (it already extends java.lang.Record).
- Sealed types plus exhaustive switches mean adding a new subtype causes a compile error everywhere you forgot to handle it — a powerful safety net.
Real-World Applications
API DTOs without Lombok
record UserDto(String id, String name, String email) {} replaces a class plus getters, equals, and hashCode — and needs no Lombok, no boilerplate, and is immutable by default.
A typed result or event model
A sealed PaymentEvent permitting Authorized, Captured, and Refunded records lets a switch handle each event exhaustively, so a new event type cannot silently slip through unhandled.
Frequently Asked Questions
When should I use a record instead of a class?
Use a record when the type is a transparent, immutable carrier of data — DTOs, value objects, coordinates, events. Use a regular class when you need mutability, inheritance, or hidden internal state with behaviour.
Are records truly immutable?
The record reference and its fields are final, so the components cannot be reassigned. However, if a component is itself a mutable object (like a List), its contents can still change — pass immutable collections for full immutability.
What problem do sealed classes solve?
They let you define a closed set of subtypes so the compiler can verify exhaustive handling. This models "one of a fixed set of cases" precisely and makes refactoring safe: add a case and the compiler flags every switch that must handle it.
Do records replace Lombok?
For immutable data carriers, largely yes — records give you constructor, accessors, equals, hashCode, and toString natively. Lombok still helps for mutable classes, builders on non-records, and other generated boilerplate.