[HN Gopher] JEP Draft: Prepare to Make Final Mean Final
       ___________________________________________________________________
        
       JEP Draft: Prepare to Make Final Mean Final
        
       Author : mfiguiere
       Score  : 115 points
       Date   : 2025-03-31 19:35 UTC (3 hours ago)
        
 (HTM) web link (openjdk.org)
 (TXT) w3m dump (openjdk.org)
        
       | Almondsetat wrote:
       | final ly
        
       | stickfigure wrote:
       | Great! Now can we make `final` the default for all fields,
       | variables, and parameters?
       | 
       | (yes yes, I know, that would break syntax... but please come up
       | with something to discourage mutability)
        
         | magicalhippo wrote:
         | Const-ness in C++ is something I miss in other languages. Being
         | immediate able to see that this function or method couldn't
         | mutate the object made it so much easier to reason about the
         | code.
         | 
         | Yeah I know there's ways around it, but then the author known
         | what they told the other party to expect.
        
           | josephg wrote:
           | Yeah I find it a bit startling going from rust (where const
           | is the default) to basically any other language. Sometimes I
           | look at typescript function definitions and I'm like -
           | uuuuhhh does this function mutate that parameter? Does it
           | keep a reference to it? If the object I'm passing is mutated
           | after I call this function, will something break? It's
           | impossible to tell from a function signature, even with all
           | of typescript's type safety. That gives me the willies. - and
           | for good reason, it's tripped me up lots of times.
           | 
           | Even the JS standard library struggles with this. You just
           | have to remember that .sort() modifies the array in place
           | (and returns it), but .slice() does a shallow clone of the
           | array. (Not a deep clone - that would be different again!)
        
             | mont_tag wrote:
             | > Even the JS standard library struggles with this. You
             | just have to remember that .sort() modifies the array in
             | place
             | 
             | ISTM that there are almost always some kinds of mutable
             | data structures present in non-trivial programs. And
             | outside of the program, you have databases to directories
             | of files that get mutated by users or other parts of a
             | program. I think this is just a fact of life.
        
               | magicalhippo wrote:
               | The point isn't that there's mutability, it's to be able
               | to easily identify where it is and where it is not.
               | 
               | Languages which lack the tools to do this are just harder
               | to reason about, at least for me.
               | 
               | In the JS example, as a non-JS coder, I would expect that
               | a sort function that returns nothing/void would sort in-
               | place, while a sort function that returns an array would
               | return a sorted copy. I would _not_ expect it to sort in-
               | place and return it.
               | 
               | However in C++ I could easily see from the function
               | definition that it might be doing that, because a sort
               | that returns a copy would take a const reference for the
               | input array. So if I came across a sort function which
               | took a non-const reference as input I'd be able to at
               | least suspect it's doing it in-place.
        
             | recursive wrote:
             | > Even the JS standard library struggles with this. ...
             | 
             | I don't think this represents struggling. There needs to be
             | _some_ way to sort in place. Sometimes you need to sort a
             | big array and don 't want to allocate.
             | 
             | And there should be _some_ way to clone the array without
             | mutating. That 's slice. So you how do you sort a clone?
             | .slice().sort()
             | 
             | I think by far the biggest problem with ES .sort() is that
             | number arrays don't sort numerically by default.
        
               | steveklabnik wrote:
               | > There needs to be some way to sort in place. Sometimes
               | you need to sort a big array and don't want to allocate
               | 
               | Your parent isn't saying there shouldn't be mutation,
               | just that the mutation should be obvious.
               | 
               | In Rust, the type signature for the in-place sort is
               | pub fn sort(&mut self)       where           T: Ord,
               | 
               | That `&mut self` lets you know that it's going to mutate.
        
               | josephg wrote:
               | Exactly. And for function parameters, rust also (usually)
               | makes it obvious when you're passing by value, by
               | reference or by mutable reference:
               | foo(x); // Moves or copies         foo(&x); // immutable
               | reference         foo(&mut x); // mutable reference
               | 
               | I don't need to look up the signature of foo to
               | understand what happens to my variable. It's obvious at a
               | glance.
        
               | recursive wrote:
               | Fair point. I guess this is the cost of dynamic
               | languages.
        
           | titzer wrote:
           | If possible, 1) design completely immutable data structures
           | that can be broadly shared and don't need to be copied. If
           | you need mutability, just embrace the fact that someone is
           | going to abuse mutability and 2) try to create abstractions
           | that can suffer abuse. If you're coming from a language that
           | doesn't have const, you learn to build things that are
           | hard(er) to screw up.
           | 
           | While bad code can exist in any language, I get worried about
           | too much const in code, because it means they failed at both
           | 1) and 2) and instead there are usually seriously tricky
           | protocols that must be observed to make the thing work. I
           | often ran into code where people were sprinkling const all
           | over the code to lock things down but they fundamentally did
           | not understand the design and made it nearly impossible to
           | evolve, unless you used casts to get rid of const, which
           | defeats the whole purpose.
           | 
           | I'm not saying const doesn't have value, but it's weapon #3,
           | not weapon #1.
        
             | magicalhippo wrote:
             | > sprinkling const all over the code to lock things down
             | 
             | That's like using a hammer on a screw, clearly not the
             | right way.
             | 
             | Thankfully I've never worked on such codebases.
        
         | duskwuff wrote:
         | What man that sees the ever-whirling wheel       Of Change, the
         | which all mortal things doth sway,       But that thereby doth
         | find, and plainly feel,       How Mutability in them doth play
         | Her cruel sports to many men's decay?
         | 
         | (Edmund Spenser, 1596)
        
         | xxs wrote:
         | >but please come up with something to discourage mutability)
         | 
         | Records?
        
       | jjmarr wrote:
       | From my perspective as a C++ developer, every attempt to use
       | `const` for compiler optimization appears to be stymied by the
       | existence of `const_cast`, because modifying a `const` value is
       | only undefined behaviour if the underlying object is `const`.
       | Glad to see that Java is willing to break the language to improve
       | it.
        
         | mb7733 wrote:
         | > modifying a `const` value is only undefined behaviour if the
         | underlying object is `const`.
         | 
         | I found this sentence confusing. You mean that modifying a
         | value that has been const_cast is undefined behaviour only if
         | the original variable was const right? Or something else?
        
           | oconnor663 wrote:
           | (Rereading your comment, it sounds like you might already
           | know all of this. Apologies.)
           | 
           | If I understand correctly, here's a C++ example that has
           | undefined behavior:                   int foo() {
           | const int x = 42;             int *p = (int *)&x;
           | *p += 1;             return x;         }
           | 
           | foo() is UB, because it modifies the const object x, using a
           | pointer cast to "cast away const". (Unfortunately, UBSan
           | doesn't catch this, and I'm not aware of any sanitizer that
           | does.) It's tempting to say that the pointer cast is "at
           | fault" for the UB, but consider this very similar example
           | that's _not_ UB:                   int bar() {
           | int x = 42;             const int *const_p = &x;
           | int *p = (int *)&const_p;             *p += 1;
           | return x;         }
           | 
           | bar() has exactly the same pointer cast as foo(), however in
           | this case the original x object is _not_ const. That makes
           | "casting away const" legal in this case. So the problem we're
           | left with, is that knowing all the types isn't enough for us
           | to tell whether this cast is going to cause UB. We have to
           | know where the pointer _originally_ came from, which might be
           | in another function or another file.
        
             | mb7733 wrote:
             | Right, gotcha, I just found it confusing to say "modifying
             | a `const` value" but if course you meant modifying the
             | underlying value [through a cast]. All good.
        
         | PhilipRoman wrote:
         | The implementation of "const" in C/C++ is really annoying, in
         | the end all you get is a bit of semantic documentation and a
         | bunch of compiler errors or casts whenever some library doesn't
         | use it. You can often trick the compiler into stronger
         | optimizations by making a local variable marked as "const" (in
         | which case it is a "true const") and copying the value to/from
         | it.
        
       | magicalhippo wrote:
       | So this is about warning about using deep reflection or such to
       | modify fields marked as final.
       | 
       | Not a Java dev, so I thought it might be related to classes
       | marked final somehow. But this seems like a reasonable proposal,
       | at least in spirit.
        
       | bironran wrote:
       | A cursory glance at "setAccessible" usage reveals popular
       | libraries such as serializers like gson and jaxb, class
       | manipulation and generation like cglib, aspectj and even
       | jdk.internal.reflect, testing frameworks and libraries including
       | junit, mockito and other mocking libraries, lombok, groovy,
       | spring, and the list goes on and on.
       | 
       | My bet is that this will be yet another "checked exception" or
       | "module system", where many applications now need to add "--add-
       | opens". If you'll use ANY of many of the more popular frameworks
       | or libraries you'll end up giving this assurance away, which will
       | make library developers not able to rely on it and we're back to
       | square one.
        
         | PathOfEclipse wrote:
         | setAccessible is also used to be able to access private fields,
         | and not just to be able to write to final fields. Most
         | libraries shouldn't need to set final fields, and I say this as
         | someone who was very against when they deprecated
         | java.lang.misc.Unsafe. I've only had to set a final field once
         | in my career and it was related to some obscure MySql/JDBC
         | driver bug/workaround. This particular deprecation seems very
         | sensible to me.
        
           | eastbound wrote:
           | So how should GSON initialize an object?
           | 
           | The theory is, go through the constructor. However, some
           | objects are designed to go through several steps before
           | reaching the desired state.
           | 
           | If GSON must deserialize {..., state:"CONFIRMED"}, it needs
           | to call new Transaction(account1, account2, amount), then
           | .setState(STARTED) then .setState(PENDING) then
           | .setState(PAID) then .setState(CONFIRMED) ? That's the theory
           | of the constructor and mutation methods guarding the state,
           | so that it is physically impossible to reach a wrong state.
           | 
           | There is a convention that deserialization is an exception to
           | this theory: It should be able to restore the object as-is,
           | after for example a transfer over the wire. So it was
           | conventionally enabled to set final variables of the object,
           | but only at initialization and only for its own good. It was
           | assumed that, even though GSON could reach a state that was
           | unachievable through normal means, it was, after all, the
           | role of the programmer to add the right annotations to avoid
           | this.
           | 
           | So how do we do it now?
        
             | vips7L wrote:
             | Why would you use GSON for objects that go through steps of
             | state? Why would you mark fields like State as final when
             | it is actually mutable? This just sounds like poorly
             | designed code.
             | 
             | Maybe I don't know of your use case, but GSON/Jackson/Json
             | type classes are strictly data that should only represent
             | the data coming over the wire. If you need to further
             | manipulate that data it sounds like the classes have too
             | much responsibility.
        
               | bdangubic wrote:
               | all state is immutable :) a change creates new state -
               | which is immutable
        
               | vips7L wrote:
               | :) no its not.
        
             | steveklabnik wrote:
             | > So how do we do it now?
             | 
             | The JEP says:
             | 
             | > the developers of serialization libraries should
             | serialize and deserialize objects using the
             | sun.reflect.ReflectionFactory class, which is supported for
             | this purpose. Its deserialization methods can mutate final
             | fields even if called from code in modules that are not
             | enabled for final field mutation.
             | 
             | I don't know enough about the details here to say if that's
             | sufficient, but I imagine that it at least should be, or if
             | it's not, it will be improved to the point where it can be.
        
         | merb wrote:
         | Yeah like the module system. Looks good on paper, is probably
         | hard to deal with. There are still tons of popular libraries
         | that have no module-info. Java does evolve, but the direction
         | it does is so weird. And than the tooling is strange and it's
         | worse that there are basically two build tools, both with their
         | upsides and downsides but they still feel more complicated than
         | tools for other languages like cargo, go (if you consider
         | that), msbuild (the modern csproj stuff/slnx)
        
         | pron wrote:
         | We've addressed that in the JEP. Serialization libraries have a
         | special way to circumvent this (until we get Serialization
         | 2.0), and mocking libraries may, indeed, need to set the flag,
         | but they're rarely used in production, so if they don't enjoy
         | some new optimisation -- no big deal.
         | 
         | BTW, this JEP does not apply to setAccessible generally, as
         | that's been restricted since JDK 16, but only to the particular
         | (and more rare) use of setAccessible to mutate instance final
         | fields. As the JEP says, static final fields, records' internal
         | instance fields, and final instance fields of hidden classes
         | cannot be mutated with that approach currently, so it's never
         | been something that's expected to work in all cases.
        
         | PaulHoule wrote:
         | My impression is that this will be painful for the code I work
         | on because the libraries you mention depend on being able to
         | modify private and/or final fields.
        
           | xxs wrote:
           | private fields are no issue
        
       | keybored wrote:
       | I don't see a way to opt-in to a hard error if this happens
       | somewhere in the guts of the code (third-party probably).
        
         | steveklabnik wrote:
         | > --illegal-final-final-mutation=deny will result in Field::set
         | throwing an IllegalAccessException for every illegal final
         | field mutation.
         | 
         | Seems like this way?
        
       | aardvark179 wrote:
       | So happy to see this, thank you Ron and Alan.
        
       | Traubenfuchs wrote:
       | I would like to see some realistic example scenarios where this
       | could actually lead to a speedup and how much it actually speeds
       | up.
        
         | kelnos wrote:
         | The article mentions one: const folding. I don't think we need
         | a benchmark to suggest that could mean a performance
         | improvement in some cases.
         | 
         | Regardless, to me this isn't about performance, this is about
         | "integrity" (to use the same term as in the JEP): consumers of
         | my library should not be mucking about in private
         | implementation details, and then inevitably complaining about
         | problems to me when something breaks. If you need a feature
         | that I don't expose, ask for it (or better yet, submit a
         | patch).
         | 
         | Sure, I've used reflection to modify library internals before,
         | but I recognize that whenever I do that I'm inviting a
         | maintenance headache into my world. But some people just think
         | things should always work, even when they are breaking them.
        
       | elric wrote:
       | I've written my fair share of nasty reflexive code for testing or
       | for abusing libraries, but I don't think I've ever overwritten
       | final fields in this way. Private fields, sure. But not final.
       | 
       | Sounds like a good evolution to me.
        
       | cogman10 wrote:
       | Hmm, not a bad approach.
       | 
       | I think the one thing that'd be nice is if I could somehow tell
       | the JVM from a class that this class is open for final mutation
       | rather than needing special flags passed into the JVM or special
       | manifests in the Jar. It's often pretty clear to me, as a dev,
       | what I when I need something to have final mutation (generally
       | only with serialization objects).
       | 
       | For example,                   @FinalMutatableByReflection
       | class Foo {           final String bar;         }
       | 
       | That'd allow me to transition code by just adding an annotation
       | where it needs to be while also getting the benefit that final is
       | really final everywhere else in code that isn't working with
       | serialization.
        
         | nightpool wrote:
         | They actually have a very similar proposal in the draft
         | already:                   The sun.reflect.ReflectionFactory
         | class only supports deserialization of objects whose classes
         | implement java.io.Serializable
         | 
         | That is, you'll still be able to mutate final fields using the
         | ReflectionFactory class, as long as that class inherits from
         | Serializable
        
           | no_wizard wrote:
           | I know this isn't the most relevant but to see the sun
           | namespace still exists gives me a small amount of joy
        
             | xxs wrote:
             | Back in the '90s I used to consider sun.xxx the cooler
             | version of Java.
        
       | nightpool wrote:
       | [Speculative optimizations] may not suffice in this case as
       | future planned optimizations may wish to rely not only on
       | immutability within the lifetime of the process, but also on the
       | immutability of fields from one run of the application to the
       | next.
       | 
       | Can someone elaborate a little more on what this means? I'm very
       | surprised to hear that this was considered a blocker important
       | enough to add all of this complicated machinery (and breaking
       | several deserialization libraries...), when I've never even heard
       | of such an optimization and can't imagine what sort of form it
       | would take
        
         | Misdicorl wrote:
         | I suppose serializing the JVM state itself to avoid the cold
         | start problem might take advantage of this?
        
           | nightpool wrote:
           | Why would that prevent the JVM from using the same
           | speculative optimization JIT with deoptimization hooks
           | approach?
        
             | Misdicorl wrote:
             | It wouldn't, but it might preclude using (future)
             | optimizations that forgo those de-optimization hooks?
        
         | pron wrote:
         | There's ongoing work, as part of Project Leyden, to cache
         | certain computations -- either performed by the user code or
         | the JVM -- from one run of the program to the next, including
         | the caching of JIT-compiled machine code. The early parts of
         | this work were discussed here:
         | https://www.morling.dev/blog/jep-483-aot-class-loading-linki...
        
         | xxs wrote:
         | This is rather old[0] but still relevant.
         | 
         |  _Surprise #2! A popular open-source framework writes to final
         | fields after object construction via generated bytecodes!...
         | This optimization of the final field is *the* main optimization
         | performed on final fields._
         | 
         | [0]
         | https://web.archive.org/web/20121016082428/http://www.azulsy...
        
       | TOGoS wrote:
       | > Application developers can avoid both current warnings and
       | future restrictions by selectively enabling the ability to mutate
       | final fields where essential.
       | 
       | /me raises hand
       | 
       | Maybe if you want to mutate a field, don't mark it `final`?
       | 
       | I know, I know, people like to pretend things are one way and
       | then hand their objects over to some horrid framework that breaks
       | all the rules, because apparently giant web of mutable spaghetti
       | is _just fine, not an anti-pattern at all_ if you let some third-
       | party bull$#!7 ORM /dependency-injection-framework-for-people-
       | who-don't-like-constructors do it.
        
       | neonsunset wrote:
       | .NET went through a similar change, blocking `static readonly`
       | fields from being accessible via private reflection.
       | Unfortunately, a lot of serializers and all sorts of meta-
       | programming libraries depend on (mutable) private reflection of
       | instance fields still so for now they are not blocked and JIT
       | cannot treat them as truly immutable, turning into JIT constants
       | the way it does so for static readonly fields. Although I guess
       | you can always make a struct and place it into a static readonly,
       | where each field could be such JIT constant (within certain
       | limits).
        
       | GauntletWizard wrote:
       | When I had the brief displeasure of working on HDFS at Facebook,
       | we took a series of customer meetings to figure out how to get
       | our oldest customers to upgrade their clusters. I was in a
       | meeting with the photos team about what their requirements were
       | and what was blocking them from upgrading, and they were very
       | frank - they asked if the upgrade preserved the internal struct
       | types associated with blocks on the disc servers. They didn't
       | actually use hdfs as a file system, they allocated 1 GB files
       | with zero replication, then used deep reflection to find the
       | extent that comprised them on the discful storage servers, then
       | built their own archival backup file system on top of that. I was
       | horrified. The some of the older hats on the team were less
       | surprised, having had some inkling of what was going on, even
       | though they clearly didn't understand the details. Others
       | considered it tantamount to sacrilege.
       | 
       | I think about this a lot. What they had built was probably
       | actually the best distributed file system within Facebook. It was
       | similarly structured to unraid, and had good availability,
       | durability, and space saving properties, but the approach to
       | engineering was just so wrong headed in my opinion that I
       | couldn't stomach it. Talking about it with other Java programmers
       | within facebook, nobody seemed to mind. Final was just a hint
       | after all.
        
         | adrianmonk wrote:
         | That reminds me of a quote from some Perl documentation[1]:
         | 
         | > _Perl does not enforce private and public parts of its
         | modules as you may have been used to in other languages like
         | C++, Ada, or Modula-17. Perl doesn 't have an infatuation with
         | enforced privacy. It would prefer that you stayed out of its
         | living room because you weren't invited, not because it has a
         | shotgun._
         | 
         | It's not exactly the same situation, but the point is, at the
         | end of the day, you need to be able to rely on the people
         | involved being willing to act reasonable. If you can't, then
         | you're going to have problems.
         | 
         | ---
         | 
         | [1] https://perldoc.perl.org/perlmodlib
        
       | exabrial wrote:
       | I'm 100% onboard with this. My thought was how are they going to
       | make Serialization work, but looks like they thought of that.
       | 
       | I was trying to think of an edge case with JsonB or JAXB that
       | would be affected by this... but generally those frameworks have
       | told you for quite awhile not to do stupid stuff like:
       | 
       | ``` @Getter public class HelloMessage { @JsonbProperty private
       | final String helloMessage; } ```
       | 
       | I can't think of any frameworks offhand that do this.
        
         | hyperpape wrote:
         | Brian Goetz, chief architect of Java, once posted a "what they
         | think I do" vs. "what I actually" do tweet. If I remember
         | correctly, 25% - 50% of the "what I actually do" category was
         | something like "get angry at serialization."
         | 
         | So I think it's safe to say "what about serialization?" is
         | always going to be asked.
        
       | xxs wrote:
       | Around 19 year late, still better than never.
        
       | ars wrote:
       | Does this mean I should start marking my variables (and function
       | parameters) with Final?
       | 
       | Up till now I always assumed the compiler would figure out on its
       | own which variables were final, and optimize as needed. But this
       | JEP makes it seem like there are optimizations that only happen
       | if you manually mark the variable.
        
       | mberning wrote:
       | If they implement this in a way similar to the package visibility
       | changes your list of JVM args is about to explode in order to
       | support legacy apps.
        
       ___________________________________________________________________
       (page generated 2025-03-31 23:00 UTC)