Interview Prep Zoneby CuriouserLabs

Question 2 of 5 · senior level

What are records — semantically, not just syntactically? When would you not use one?

🎤 Say this first

A record is a nominal tuple with a semantic contract: it declares that the class is its components — transparent, shallowly immutable carriers where equals/hashCode/toString/accessors derive from state. The compact constructor validates/normalizes; you can add methods and implement interfaces; you cannot extend a class or add instance fields. Don't use one when identity matters (JPA entities), when state must mutate, when you need hidden/derived state, or when the 'components = API' transparency is wrong for the abstraction.

The full picture

Records with discipline
public record Money(BigDecimal amount, Currency currency) {
    public Money {                                    // compact constructor
        Objects.requireNonNull(currency);
        if (amount.scale() > currency.getDefaultFractionDigits())
            throw new IllegalArgumentException("scale");
        amount = amount.setScale(currency.getDefaultFractionDigits()); // normalize
    }
    public Money add(Money other) {                   // behavior is welcome
        if (!currency.equals(other.currency)) throw new IllegalArgumentException();
        return new Money(amount.add(other.amount), currency);
    }
}
// Shallow immutability trap:
record Batch(List<Item> items) {
    Batch { items = List.copyOf(items); }             // copy or stay mutable!
}
  • Where they shine: DTOs, API request/response bodies, compound map keys, domain value objects, multi-value returns, messages between threads.
  • Why not JPA entities: entities have identity + mutable lifecycle + lazy proxies — the exact opposite of a record's contract (no no-arg ctor, final fields).
  • vs Lombok @Data/@Value: records are a language feature with semantics (serialization uses the canonical constructor — validation runs on deserialize!, pattern-matching deconstruction); Lombok is codegen with none of those guarantees.
  • Local records inside methods are underrated for naming intermediate stream results.

🔄 Likely follow-up questions

  • How do records interact with Jackson/validation?
  • What are record patterns (Java 21) and how do they change consuming code?
  • How do 'withers' work today (toBuilder-style copies) and what may come from derived record creation?