skip to content

Records

Records give you a transparent immutable data carrier with generated accessors, equals, hashCode and toString. Interviewers ask what they generate, what they forbid, and when a record is the wrong choice.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

15

What is a Java record, and what does the compiler generate for you from its components?

level: juniorimportance: must knowfreq 78%

answer

  1. record Header(components) {}
  2. private final field + same-name accessor per component
  3. auto equals / hashCode / toString from all components
  4. accessor is x(), not getX()
  5. implicitly final, extends java.lang.Record

basics

~20 s

A record is a compact class for holding immutable data. You list its components in the header, and the compiler auto-generates a private final field plus an accessor for each, a constructor, and equals, hashCode, and toString.

solid answer

~40 s

A record is a special class declared as `record Point(int x, int y) {}`. The names in parentheses are its components. For each component the compiler creates a `private final` field and a public accessor method named exactly after the component (e.g. `x()`, not `getX()`). It also generates a canonical constructor that takes all components, plus value-based `equals`, `hashCode`, and `toString` derived from all components. The result is an immutable data carrier with almost no boilerplate. Records are implicitly `final`, extend `java.lang.Record`, and cannot extend another class. Introduced as a preview in Java 14 and finalized in Java 16, records are ideal for DTOs, map keys, and return types that bundle several values.

code

java · 11 lines
java
public record Point(int x, int y) { }

// Usage
Point p = new Point(1, 2);
int a = p.x();          // accessor named after the component (not getX)
int b = p.y();
System.out.println(p);  // Point[x=1, y=2]  -> generated toString

Point q = new Point(1, 2);
System.out.println(p.equals(q)); // true  -> value-based equals
System.out.println(p.hashCode() == q.hashCode()); // true

go deeper

for a junior

Can declare a record, list its components, and use the generated accessor, equals, and toString. Knows accessors are x() not getX().

for a middle

Explains exactly what the compiler generates (field, accessor, canonical constructor, equals/hashCode/toString) and that records are immutable and implicitly final.

for a senior

Articulates the design intent (transparent immutable data carrier), the value-based equality contract derived from all components, the java.lang.Record superclass constraint, and good use cases (DTOs, keys, return types) vs. where a normal class is better.

for a principal

Frames records as nominal product types / value-based aggregates, discusses how value-based equality enables safe use as keys and in pattern-matching deconstruction, and weighs API/compatibility implications of exposing records across module or library boundaries.

## What problem records solve Before records, a simple immutable data class in Java required a lot of repetitive ("boilerplate") code: a private field per value, a constructor, a getter per field, and hand-written `equals`, `hashCode`, and `toString`. This was tedious and error-prone. A **record** is a concise way to declare a class whose main purpose is to **carry data**. ## Anatomy of a declaration ```java public record Point(int x, int y) { } ``` - The keyword `record` replaces `class`. - The list in parentheses `(int x, int y)` is the **record header**, and each entry (`x`, `y`) is a **component**. A component is just a named, typed piece of data the record holds. - The body `{ }` can be empty — everything essential is generated. ## What the compiler generates From the components, the compiler automatically produces: 1. **A `private final` field per component.** `final` means it is assigned once and never changes — this is what makes records immutable by default. (Immutable = its state cannot be modified after construction.) 2. **A public accessor method per component**, named *exactly* after the component — `x()` and `y()`, **not** `getX()`/`getY()`. Java records deliberately drop the JavaBeans `get` prefix. 3. **A canonical constructor** taking all components in declared order, which assigns each argument to the matching field. 4. **`equals(Object)`** — two records are equal when they are the same record type and *all* their components are equal (value-based equality, not reference identity). 5. **`hashCode()`** — derived from all the components, consistent with `equals`. 6. **`toString()`** — a readable form like `Point[x=1, y=2]`. ## Built-in constraints - A record is **implicitly `final`** — you cannot subclass it. - It **implicitly extends `java.lang.Record`** — so it cannot extend any other class (Java has single inheritance of implementation), but it *can* implement interfaces. - You may not add extra **instance** fields beyond the components (static fields are allowed). ## When to use one Records shine for **DTOs** (data transfer objects), **value objects**, multi-value return types, and **map/set keys** (because correct `equals`/`hashCode` come for free). Avoid them when you need mutable state or an inheritance hierarchy. ## History Records were a preview feature in Java 14 (2020) and became a permanent language feature in Java 16 (2021).

  • Why are record accessors named x() instead of getX()?
    Records intentionally break with the JavaBeans get/set convention. The accessor name matches the component name exactly, signaling that a record is a transparent data carrier rather than a mutable bean. The language designers chose `x()` to read as 'the x of this record' and to allow component names to drive the API directly.
  • Can a record have zero components?
    Yes. `record Empty() {}` is legal — it produces a class with no fields, a no-arg canonical constructor, and equals/hashCode/toString based on no components (so all instances are equal). It is occasionally used as a sealed-hierarchy marker.

Think of a record as a pre-printed shipping label: you fill in the fixed boxes (the components) once, the label is sealed (immutable), and anyone can read the boxes back — but nobody re-prints over them.

saying these in an interview costs you the question

  • Calling the accessor getX() — records use x() with no get prefix.
  • Saying records are mutable — components are private final, so a record's own fields cannot be reassigned.
  • Claiming the compiler generates setters — records have no setters by design.
  • Saying a record can extend another class — it implicitly extends java.lang.Record and Java forbids multiple class inheritance.
  • Confusing a record with a regular class that simply has fewer lines — the generated equals/hashCode/toString and the final-field guarantees are part of the language contract.

context

open as a page

What is the canonical constructor of a Java record, and what does the compiler generate if you don't write one?

level: juniorimportance: must knowfreq 70%

basics

~20 s

The canonical constructor is a record's main constructor whose parameters match its components, one-for-one, by name and type. If you don't write one, the compiler generates it for you, and it just assigns each parameter to the matching field.

open as a page

What members can you add to a Java record, and what is forbidden?

level: juniorimportance: must knowfreq 78%

basics

~20 s

You can add instance methods, static fields, static methods, and nested types. You can also override the auto-generated accessors, equals, hashCode, and toString. You cannot declare extra instance fields beyond the ones in the record header.

open as a page

How does a record's generated equals, hashCode, and toString behave, and what is the resulting equality contract?

level: middleimportance: must knowfreq 70%

basics

~20 s

Two records are equal if they are the same record type and every component is equal. hashCode is computed from all components and stays consistent with equals. toString prints the type name and each component, like Point[x=1, y=2].

open as a page

What is a compact canonical constructor in a Java record, and how does field assignment work in it?

level: middleimportance: must knowfreq 72%

basics

~20 s

A compact constructor uses the record's name with no parameter list and no field assignments. You write only validation or cleanup code; the compiler automatically assigns all the components to their fields at the very end.

open as a page

Explain the canonical constructor and the compact constructor in records, including the rules for overriding accessors.

level: middleimportance: must knowfreq 70%

basics

~20 s

Every record has a canonical constructor that takes all components. You can write it explicitly, or use a compact constructor (no parameter list) to validate or normalize inputs - the fields are assigned for you afterward. You can also override accessors, but they must return the right type.

open as a page

When should you choose a record over a regular class, and what are the trade-offs?

level: seniorimportance: must knowfreq 62%

basics

~20 s

Use a record when you need an immutable carrier of data whose identity is its values - like a DTO, a key, or a return tuple. Avoid records when you need mutability, inheritance, hidden/derived state, or JavaBean-style getters that frameworks expect.

open as a page

What is the canonical constructor of a record, and how do the compact and explicit canonical forms differ?

level: juniorimportance: should knowfreq 52%

basics

~20 s

The canonical constructor takes exactly the record's components, in order, and assigns each to its field. The compact form omits the parameter list and lets you validate or normalize the arguments; the fields are assigned automatically at the end. An explicit canonical constructor writes the parameter list and assignments yourself.

open as a page

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%

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.

open as a page

Beyond the canonical constructor, what other constructors can a record declare, and what is the one rule they must follow?

level: middleimportance: should knowfreq 50%

basics

~20 s

A record can have extra constructors with different parameter lists (overloads), for example to supply defaults. The rule: every non-canonical constructor must call another constructor with this(...) as its first statement, eventually reaching the canonical one.

open as a page

What are nested and local records, and how do they differ from nested or local regular classes?

level: middleimportance: should knowfreq 48%

basics

~20 s

You can declare a record inside another type (nested) or inside a method body (local). Unlike inner classes, nested and local records are always static-like - they have no reference to an enclosing instance and cannot capture the outer object's state the way an inner class can.

open as a page

A record's components are private final fields — does that make a record fully immutable? Explain the limits.

level: seniorimportance: should knowfreq 55%

basics

~20 s

The record's own fields can't be reassigned, so the record is shallowly immutable. But if a component is a reference to a mutable object (like a List or array), callers can still change that object's contents through the shared reference.

open as a page

How do you keep a Java record truly immutable when one of its components is a mutable type like a List or a Date?

level: seniorimportance: should knowfreq 58%

basics

~20 s

A record's fields are final but a mutable component (like a List) can still be changed through the caller's reference. Make a defensive copy in the canonical constructor on the way in, and return a copy from the accessor on the way out.

open as a page

How does serialization differ for records compared to ordinary serializable classes?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Records serialize using only their components, and deserialization always goes through the canonical constructor. So validation in that constructor runs on the way back in, and you cannot customize the process with the usual readObject/writeObject hooks the way you can for normal classes.

open as a page

Why is the canonical constructor the right place to enforce a record's invariants, and how does that interact with deserialization and equals/hashCode?

level: principalimportance: nice to knowfreq 38%

basics

~10 s

All ways of building a record run its canonical constructor, including Java deserialization, so validation placed there always runs. That keeps every instance valid and keeps the auto-generated equals/hashCode consistent with the components.

open as a page