skip to content

When would you choose a record over a regular class, and what does a record forbid that a normal class allows?

level: middleimportance: should knowfreq 60%

answer

  1. record = immutable data carrier with free equals/hashCode/toString
  2. good for DTO, value object, key, multi-return, sealed leaf
  3. forbidden: subclass, extend a class, extra instance fields, mutability
  4. allowed: implement interfaces, methods, compact constructor, override accessor
  5. class wins for mutable state / inheritance / hidden fields

basics

~20 s

Use 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 s

Reach 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

for a junior

Knows records are for simple data holders and that you cannot subclass one or add setters.

for a middle

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.

for a senior

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.

for a principal

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.

context