Where the claim is true, and stronger than usual
Start by giving the teammate their due, because the claim is partly right. A record's component fields are final, and Java records are also protected from a trick that defeats ordinary final fields. The Field.set Javadoc says a final field is writable through reflection only when setAccessible(true) succeeded and the declaring class is neither hidden nor a record. So a library cannot reflectively overwrite a record field the way it can with a normal class.
Records also deserialise through their canonical constructor, so any validation you put there runs on serialised input too. For a value type such as a money amount or a date range, that is a real improvement over a hand-written class.
Where it is false: shallow immutability
The final field holds a reference, and the reference cannot change. The object behind it can.
record Basket(String id, List<String> items) { }
List<String> source = new ArrayList<>(List.of("tea"));
Basket basket = new Basket("b1", source);
source.add("coffee");
basket.items().clear();
The record did nothing wrong by its own rules. It stored what it was given and returned what it stored. The fix is to copy on the way in.
record Basket(String id, List<String> items) {
Basket {
items = List.copyOf(items);
}
}
List.copyOf returns an unmodifiable list, so callers can no longer mutate it, and it throws NullPointerException on a null list or a null element. That is usually the behaviour you want, but say it out loud, because it changes what the constructor accepts. If the input is already an unmodifiable list, copyOf may return it unchanged rather than copying.
Copying the list does not copy its elements. A List<LineItem> where LineItem has setters is still mutable at the second level. Immutability does not propagate through a record: each level is only as immutable as its own type. Arrays need copying in both directions, with clone() in the compact constructor and an overridden accessor that returns another clone, because there is no unmodifiable array. A java.util.Date component should usually become an Instant, which is immutable by design.
The consequence that bites in production is equality. A record's equals and hashCode cover every component, so a record whose list changes after it was placed in a HashSet now hashes to a different bucket. The modelling page on records and sealed types covers that and the array-equality quirk in more depth.
Why a record cannot be a JPA entity
The Jakarta Persistence specification states the requirements for an entity class: it must have a public or protected no-arg constructor, it must not be final, and no persistent instance variables may be final. A record is implicitly final, its fields are final, and it has no no-arg constructor. You can declare one that delegates to the canonical constructor, but the fields it sets can never be changed afterwards.
Those rules are not arbitrary. A provider creates the object first and then writes the column values into it, which a record's final fields forbid. Lazy loading works by generating a subclass proxy, and a final class cannot be subclassed. Dirty checking compares the managed instance with a snapshot at flush time. Changing a record means building a new instance, and the persistence context is not tracking that new instance.
There is also a disagreement about identity. A JPA entity is identified by its primary key: the same row loaded twice with different field values is still the same customer. A record is identified by all of its components, so two states of the same row are unequal. Those are opposite definitions of equality, and choosing a record picks the wrong one for an entity.
Where records belong near persistence
The useful answer does not stop at "no". Records fit well on the read side and as value types inside an entity.
@Entity
class Customer {
@Id @GeneratedValue Long id;
String name;
protected Customer() { }
}
record CustomerSummary { }
List<CustomerSummary> rows = em.createQuery(
,
CustomerSummary.class).getResultList();
Hibernate 6.2 added support for records as embeddable types, which suits a Money(amount, currency) or an Address. Updating one means replacing the whole embedded value, which is the right semantics for a value.
The half-truth that gets past code review
The claim survives review because it is half right, and a reviewer who hears "records are immutable" agrees with it. A strong answer separates two meanings. A reference that cannot be reassigned is one; an object graph nobody can change is the other. A record guarantees the first and only delivers the second if every component type is itself immutable or copied. The JPA objection is a separate argument and needs its own evidence: cite the specification's rules on finality and constructors, not a general sense that records are meant for DTOs.
A record promises that its fields will not be reassigned. Whether the data stays unchanged depends on every component type, and an entity needs mutability and identity, which a record is built to rule out.
© 2026 Preptima. Originally published at preptima.com (opens in a new tab).