JEP 468 updating non-updatable fields

Ryan Leach rleach at rleach.id.au
Mon Jan 26 09:09:08 UTC 2026


Whilst I agree with Brian, is this not a smell though if it can't be
restricted in this way, that the proposed additions aren't flexible enough
to ensure program correctness in what's surely going to be a fairly common
pattern?

Maybe withers shouldn't just be a syntactic optimisation for exactly this
reason.

-- Ryan


On Mon, 26 Jan 2026, 5:40 am Brian Goetz, <brian.goetz at oracle.com> wrote:

> The important mental model here is that a reconstruction (`with`)
> expression is "just" a syntactic optimization for:
>
>  - destructure with the canonical deconstruction pattern
>  - mutate the components
>  - reconstruct with the primary constructor
>
> 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.
>
> Primary constructors can enforce invariants _on_ or _between_ components,
> such as:
>
>     record Rational(int num, int denom) {
>         Rational { if (denom == 0) throw ... }
>     }
>
> or
>
>     record Range(int lo, int hi) {
>         Range { if (lo > hi) throw... }
>     }
>
> 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.  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
>
>     rec with { dbId++ }
>
> *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.
>
> 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.
>
>
>
>
> On 1/25/2026 1:37 PM, Andy Gegg wrote:
>
> Hello,
> 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.
>
> Since  carrier classes and records are for data, in a database application
> somewhere or other you're going to get database ids in records:
> record MyRec(int dbId, String name,...)
>
> While everything is immutable this is fine but JEP 468 opens up the
> possibility of mutation:
>
> MyRec rec = readDatabase(...);
> rec = rec with {name="...";};
> writeDatabase(rec);
>
> which is absolutely fine and what an application wants to do.  But:
> MyRec rec = readDatabase(...);
> rec = rec with {dbId++;};
> writeDatabase(rec);
>
> is disastrous.  There's no way the canonical constructor invoked from
> 'with' can detect stupidity nor can whatever the database access layer does.
>
> 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).
>
> Here there is nothing to say "do not update this!!!" except code comments,
> JavaDoc and naming conventions.
>
> It's not always obvious which fields may or may not be changed in the
> application.
>
> record MyRec(int dbId, int fatherId,...)
> probably doesn't want
> rec = rec with { fatherId = ... }
>
> but a HR application will need to be able to do:
>
> record MyRec(int dbId, int departmentId, ...);
> ...
> rec = rec with { departmentId = newDept; };
>
> 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.
>
> 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.
>
> Thank you for your time,
> Andy Gegg
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20260126/a64421c9/attachment.htm>


More information about the amber-dev mailing list