<div dir="auto"><div>I'm alluding to state beyond the right to choose ids, but yes: (<id, misc. bookkeeping>, PersonData)</div><div dir="auto"><br></div><div dir="auto"><br><br><div class="gmail_quote gmail_quote_container" dir="auto"><div dir="ltr" class="gmail_attr">On Mon, Jan 26, 2026, 11:15 AM Brian Goetz <<a href="mailto:brian.goetz@oracle.com">brian.goetz@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><u></u>
<div>
<font size="4" face="monospace">I think we might be saying the same
thing. When I say "regular class", it doesn't matter whether it
is hand written or generated; the key is that the right to choose
IDs is reserved by that class. Maybe its hidden behind an
interface, maybe not. But you have an _entity_ class that
associates (id, PersonData) and a record/carrier for PersonData,
which is _just_ the data. <br>
</font><br>
<div>On 1/26/2026 11:05 AM, Ethan McCue
wrote:<br>
</div>
<blockquote type="cite">
<div dir="auto">Forgive me if I'm mixing up terms here, but I
think a database entity when fetched from some persistence
context can actually be a runtime generated subclass that also
maintains a reference to the context that produced it.
<div dir="auto"><br>
</div>
<div dir="auto">So you'd have an instance of Person and be
tempted to write p = p with { name = "..."; }. This could
maybe work if the subclass could do its own thing, but if you
are feeding the data back into new Person(...) it can't.</div>
<div dir="auto"><br>
</div>
<div dir="auto"><br>
</div>
</div>
<br>
<div class="gmail_quote">
<div dir="ltr" class="gmail_attr">On Mon, Jan 26, 2026, 10:55 AM
Brian Goetz <<a href="mailto:brian.goetz@oracle.com" target="_blank" rel="noreferrer">brian.goetz@oracle.com</a>>
wrote:<br>
</div>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div> <font size="4" face="monospace">I think the
entity-modeling story here is more like:<br>
<br>
- a regular class that associates (id, PersonInfo), where
the ids are dispensed exclusively by the ORM, and<br>
- a record/carrier for PersonInfo, that lets you "mutate"
the information but not the ID association. <br>
<br>
<br>
</font><br>
<div>On 1/26/2026 10:52 AM, Aaryn Tonita wrote:<br>
</div>
<blockquote type="cite"> This past sprint we had such a case
where unconditional deconstruction would have helped with
database entities. Basically a user had created a patient
twice over quite some time span and operations and graft
allocations were associated with both patients but the
medical and personal details were most accurate on the
newest and the user desire was to merge them. Our
deduplication detection didn't trigger because of
incompleteness of the old record. However because so many
downstream systems depend on the oldest record that was
the id to keep.
<div><br>
</div>
<div>In the database you would just alter the id of the
old with cascade, then relink the foreign key
constraints of related tables to the new patient, then
modify the id back to the old value again with cascade
and delete the old. Now JPA doesn't support this
approach of altering a primary key... So instead you
need to fetch the new and old entity and deconstruct the
new entity before deleting it and then reconstruct the
old entity with the new data and same old id... Or you
reach for the @Query approach instead and after
modifying the database you change just the id (and
linked relations) of the newer patient representation
object. This latter approach is less brittle to future
changes. </div>
<div><br>
</div>
<div>But the original point that Brian made stands: the
constructor always allows a nonsense representation.
People exploit that in unit tests to create
unpersisted entities or relations to other entities that
don't exist. Without fetching the entire database all at
once you won't really get away from that but I also
wouldn't want to.</div>
<div><br>
</div>
<div>We have more and more places where withers would help
(and sad places where a carrier class would have helped
but we used a class in place of a record). <br>
<br>
<br>
<br>
<br>
Sent from <a href="https://urldefense.com/v3/__https://proton.me/mail/home__;!!ACWV5N9M2RV99hQ!M9c8nZS-3Ga3cPLqwU5QkNrUJs_RoN8etK5ZrHbzmmz29wzkUXoSJmTLdIVhe9GYuWhACJi2C0eIRWF10YI$" rel="noreferrer noreferrer noreferrer" target="_blank">Proton Mail</a> for Android. </div>
<br>
<div><br>
<br>
-------- Original Message --------<br>
On Monday, 01/26/26 at 16:13 Ethan McCue <a href="mailto:ethan@mccue.dev" rel="noreferrer noreferrer" target="_blank"><ethan@mccue.dev></a>
wrote:<br>
<blockquote>
<div dir="auto">
<div>My immediate thought (aside from imagining
Brian trapped in an eternal version of that
huffalumps and woozles scene from Winnie the Pooh,
but it's all these emails) is that database
entities aren't actually good candidates for
"unconditional deconstruction" </div>
<div dir="auto"><br>
</div>
<div dir="auto">I think this because the act of
getting the data from the db/persistence context
is intrinsically fallible *and* attached to
instance behavior; maybe we need to look forward
to what the conditional deconstruction story would
be?</div>
<div dir="auto"><br>
<div class="gmail_quote" dir="auto">
<div dir="ltr" class="gmail_attr">On Mon, Jan
26, 2026, 10:04 AM Brian Goetz <<a href="mailto:brian.goetz@oracle.com" rel="noreferrer noreferrer noreferrer" target="_blank">brian.goetz@oracle.com</a>>
wrote:<br>
</div>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px solid #ccc;padding-left:1ex">
<div> <br>
<br>
<blockquote type="cite">
<div dir="ltr">
<div>It's interesting that when language
designers make the code easier to
write, somebody may complain that it's
too easy :-)</div>
</div>
</blockquote>
<br>
I too had that "you can't win" feeling :)<br>
<br>
I would recast the question here as "Can
Java developers handle carrier classes".
Records are restricted enough to keep
developers _mostly_ out of trouble, but the
desire to believe that this is a syntactic
and not semantic feature is a strong one,
and given that many developers education
about how the language works is limited to
"what does IntelliJ suggest to me", may not
even _realize_ they are giving into the dark
side. <br>
<br>
I think it is worth working through the
example here for "how would we recommend
handling the case of a "active" row like
this. <br>
<br>
<blockquote type="cite">
<div dir="ltr">
<div>I think it's a perfect place for
static analysis tooling. One may
invent an annotation like
`@NonUpdatable` </div>
<div>with the `RECORD_COMPONENT` target
and use it on such fields, then create
an annotation processor </div>
<div>(ErrorProne plugin, IntelliJ IDEA
inspection, CodeQL rule, etc.), that
will check the violations and fail the
build if there are any.</div>
<div>Adding such a special case to the
language specification would be an
overcomplication.</div>
<div><br>
</div>
<div>With best regards,<br>
Tagir Valeev.</div>
</div>
<br>
<div class="gmail_quote">
<div dir="ltr" class="gmail_attr">On
Sun, Jan 25, 2026 at 11:48 PM Brian
Goetz <<a href="mailto:brian.goetz@oracle.com" rel="noreferrer noreferrer noreferrer" target="_blank">brian.goetz@oracle.com</a>>
wrote:<br>
</div>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px solid #ccc;padding-left:1ex">
<div> <font size="4" face="monospace">The
important mental model here is
that a reconstruction (`with`)
expression is "just" a syntactic
optimization for:<br>
<br>
- destructure with the canonical
deconstruction pattern<br>
- mutate the components <br>
- reconstruct with the primary
constructor<br>
<br>
So the root problem here is not
the reconstruction expression; if
you can bork up your application
state with a reconstruction
expression, you can bork it up
without one. <br>
<br>
</font><font size="4" face="monospace">Primary
constructors can enforce
invariants _on_ or _between_
components, such as:<br>
<br>
record Rational(int num, int
denom) { <br>
Rational { if (denom == 0)
throw ... }<br>
}<br>
<br>
or<br>
<br>
record Range(int lo, int hi)
{ <br>
Range { if (lo > hi)
throw... }<br>
}<br>
<br>
What they can't do is express
invariants between the record /
carrier state and "the rest of the
system", because they are supposed
to be simple data carriers, not
serialized references to some
external system. </font><font size="4" face="monospace">A class
that models a database row in this
way is complecting entity state
with an external entity id. By
modeling in this way, you have
explicitly declared that <br>
<br>
rec with { dbId++ }<br>
<br>
*is explicitly OK* in your system;
that the components of the record
can be freely combined in any way
(modulo enforced cross-component
invariants). And there are
systems in which this is fine!
But you're imagining (correctly)
that this modeling technique will
be used in systems in which this
is not fine. <br>
</font><font size="4" face="monospace"><br>
The main challenge here is that
developers will be so attracted to
the syntactic concision that they
will willfully ignore the semantic
inconsistencies they are
creating. <br>
<br>
<br>
</font><br>
<br>
<div>On 1/25/2026 1:37 PM, Andy Gegg
wrote:<br>
</div>
<blockquote type="cite"> <font face="Times New Roman">Hello,<br>
I apologise for coming late to
the party here - Records have
been of limited use to me but Mr
Goetz's email on carrier classes
is something that would be very
useful so I've been thinking
about the consequences.<br>
<br>
Since carrier classes and
records are for data, in a
database application somewhere
or other you're going to get
database ids in records:<br>
record MyRec(int dbId, String
name,...)<br>
<br>
While everything is immutable
this is fine but JEP 468 opens
up the possibility of mutation:<br>
<br>
</font><font face="Times New Roman">MyRec rec</font><font face="Times New Roman"> =
readDatabase(...);<br>
rec = rec with {name="...";};<br>
writeDatabase(rec);<br>
<br>
which is absolutely fine and
what an application wants to
do. But:<br>
</font><font face="Times New Roman">MyRec </font><font face="Times New Roman">rec =
readDatabase(...);<br>
rec = rec with {dbId++;};<br>
writeDatabase(rec);<br>
<br>
is disastrous. There's no way
the canonical constructor
invoked from 'with' can detect
stupidity nor can whatever the
database access layer does.<br>
<br>
In the old days, the lack of a
'setter' would usually prevent
stupid code - the above could be
achieved, obviously, but the
code is devious enough to make
people stop and think (one
hopes).<br>
<br>
Here there is nothing to say "do
not update this!!!" except code
comments, JavaDoc and naming
conventions.<br>
<br>
It's not always obvious which
fields may or may not be changed
in the application.<br>
<br>
record </font><font face="Times New Roman">MyRec(int
dbId, int fatherId,...)<br>
probably doesn't want<br>
rec = rec with { fatherId = ...
}<br>
<br>
but a HR application will need
to be able to do:<br>
<br>
record </font><font face="Times New Roman">MyRec(int
dbId, int departmentId, ...);<br>
...<br>
rec = rec with { </font><font face="Times New Roman">departmentId
= newDept; };<br>
<br>
Clearly, people can always write
stupid code (guilty...) and the
current state of play obviously
allows the possibility (rec =
new MyRec(rec.dbId++, ...);)
which is enough to stop people
using records here but carrier
classes will be very tempting
and that brings derived creation
back to the fore.<br>
<br>
It's not just database ids which
might need restricting from
update, e.g. timestamps (which
are better done in the database
layer) and no doubt different
applications will have their own
business case restrictions.<br>
<br>
Thank you for your time,<br>
Andy Gegg<br>
</font><br>
</blockquote>
<br>
</div>
</blockquote>
</div>
</blockquote>
<br>
</div>
</blockquote>
</div>
</div>
</div>
</blockquote>
</div>
</blockquote>
<br>
</div>
</blockquote>
</div>
</blockquote>
<br>
</div>
</blockquote></div></div></div>