<div dir="ltr">Hello!<div><br></div><div>It's interesting that when language designers make the code easier to write, somebody may complain that it's too easy :-)</div><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 gmail_quote_container"><div dir="ltr" class="gmail_attr">On Sun, Jan 25, 2026 at 11:48 PM Brian Goetz <<a href="mailto:brian.goetz@oracle.com">brian.goetz@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><u></u>

  
  <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>