When would you choose a record over a regular class, and what does a record forbid that a normal class allows?
answer
- record = immutable data carrier with free equals/hashCode/toString
- good for DTO, value object, key, multi-return, sealed leaf
- forbidden: subclass, extend a class, extra instance fields, mutability
- allowed: implement interfaces, methods, compact constructor, override accessor
- class wins for mutable state / inheritance / hidden fields
basics
~20 sUse a record when you mainly need an immutable bundle of data with sensible equals, hashCode, and toString — DTOs, value objects, map keys, return types. A record can't extend another class, can't be subclassed, and can't add extra instance fields.
solid answer
~40 sReach for a record when the type's job is to **carry a fixed set of values immutably** and you want correct value-based `equals`/`hashCode`/`toString` for free: DTOs, API request/response objects, multi-value return types, composite map/set keys, and the leaves of sealed hierarchies. Use a regular class when you need **mutable state**, an **inheritance hierarchy**, hidden/derived internal fields, or fine control over which fields define equality. Records forbid several things a class allows: they are implicitly `final` (no subclassing), they implicitly extend `java.lang.Record` (so they cannot `extends` any other class), and you cannot declare additional **instance** fields beyond the components (static fields and methods are fine). They *can* implement interfaces, add instance methods, override accessors, validate in a compact constructor, and be nested or local. So records trade flexibility for a guaranteed, boilerplate-free immutable data contract.
go deeper
Knows records are for simple data holders and that you cannot subclass one or add setters.
Lists concrete use cases (DTO, value object, key, return type) and the core restrictions (final, extends java.lang.Record, no extra instance fields, immutable) vs. what a class offers.
Chooses correctly between record and class by trade-off, knows records can implement interfaces / add methods / validate via a compact constructor, and pairs records with sealed types and pattern matching.
Sets architectural guidance on modeling value objects as records, evolution/compatibility of record components in public APIs, and the interplay of records with sealed hierarchies, serialization, and pattern-matching exhaustiveness.
## The decision in one line Use a **record** when a type's identity *is* its data; use a **class** when a type has behavior, mutable state, or a hierarchy. ## What a record is optimized for A record is a **transparent, immutable data carrier**. 'Transparent' means its public API (the accessors) directly exposes its construction state. Because the compiler generates value-based `equals`, `hashCode`, and `toString` from the components, records are ideal for: - **DTOs / API payloads** — bundle request or response fields with no boilerplate. - **Value objects** — `Money(amount, currency)`, `Point(x, y)` — where equality means 'same values.' - **Composite keys** — a multi-field `HashMap`/`HashSet` key works correctly out of the box. - **Multi-value returns** — return `record Result(int code, String message)` instead of an out-parameter or an array. - **Sealed hierarchy leaves** — records pair naturally with `sealed` interfaces and pattern-matching `switch` (record deconstruction patterns). ## What records forbid (vs. a normal class) 1. **No subclassing.** A record is *implicitly `final`*. You cannot extend it. 2. **No extending a class.** A record *implicitly extends `java.lang.Record`*; since Java allows only single class inheritance, a record cannot `extends` anything else. (It *can* `implements` interfaces.) 3. **No extra instance fields.** All instance state must be declared as components in the header. You may add `static` fields and any methods, but not additional non-static fields. This is what keeps the record 'transparent' — its state is exactly its components. 4. **No mutability.** Components are `private final`; there are no setters. ## What records still allow - Implementing interfaces, adding instance and static methods. - A **compact constructor** for validation/normalization, or a fully custom canonical constructor; additional non-canonical constructors must delegate to the canonical one. - Overriding any generated member (an accessor, `equals`, `hashCode`, `toString`). - Being declared `public/protected/...`, nested, or **local** (inside a method). ## When a regular class wins - You need **mutable** state (a counter, a builder-style accumulator, an entity whose fields change). - You need an **inheritance hierarchy** with shared implementation (records can't extend a class and can't be extended). - You need **encapsulated / derived internal fields** that aren't part of the public construction contract. - You need **custom equality** that ignores some fields, or no value semantics at all. ## Mental model If you would have written a class with `private final` fields, a constructor, getters, and `equals/hashCode/toString` — that's a record. If you'd have mutators, inheritance, or hidden state — keep the class.
- Can a record implement an interface or be part of a sealed hierarchy?Yes. A record cannot extend a class, but it can implement any number of interfaces, and it pairs especially well as a leaf (permitted implementation) of a sealed interface — enabling exhaustive pattern-matching switches with record deconstruction.
- You need a data type with one derived field computed from the others. Record or class?Still a record. Declare only the source values as components and expose the derived value as an ordinary instance method (e.g. `double area()` on a `Rectangle(width, height)`). You cannot add it as an extra instance field, but a computed method keeps the record transparent and immutable.
saying these in an interview costs you the question
- Saying a record can extend another class — it implicitly extends java.lang.Record and cannot.
- Trying to add a non-static instance field beyond the components.
- Subclassing a record — records are implicitly final.
- Using a record for mutable, entity-like state that changes over time.
- Assuming a record cannot have methods — it can have instance and static methods, just no extra instance fields.