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 2 days ago [-]
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 2 days ago [-]
I know this isn’t the most relevant but to see the sun namespace still exists gives me a small amount of joy
xxs 2 days ago [-]
Back in the '90s I used to consider sun.xxx the cooler version of Java.
owlstuffing 2 days ago [-]
The issue is that many essential libraries and tools rely on setting internal final fields. I assume that's why the options around this have remained open-ended.
The problem with these various "integrity by default" options is that, in most cases, granting access to one effectively grants access to all. For instance, JNI, agent libraries, and JPMS options can each be used to bypass restrictions, making the separation between them largely illusory. Integrity, as framed here, is ultimately binary.
The unfortunate reality of the "integrity by default" crusade is that applications relying on libraries and tools that modify internals will continue to do so. The JDK hasn’t filled any gaps—it has only made an already delicate situation worse.
pron 2 days ago [-]
First, it's not a "crusade" but the steps necessary to deliver the features Java's users demand. Second, the prevalence of the use of JDK internals has dropped drastically, and demonstrably so. For example, many programs broke before internals were encapsulated during the upgrade from 8 to 9; 99% of the causes were libraries relying on internals, which had changed. Access to internals was closed off in JDK 16, although, as you say, it can be selectively allowed. And yet, between JDK 17 and JDK 23, changes of similar magnitude to the JDK internals caused nearly no upgrade problems. Upgrading the JDK now is smoother and easier than it's been in the last two decades. Why? Because there's been a large reduction in libraries' access to internals.
I think Java's handling of this transition compares very favourably to how other languages have handled similar transitions from some old model to a new one (or evolution in general) in terms of balancing the needs of both old and new projects.
cogman10 2 days ago [-]
I can attest to how easy it's become to update jdks for our org.
8->11 was really a pain in the neck. 11->17 had some pain, but mostly was nothing serious.
17->21 has been painless.
And I have some projects running on 24 already with no problems.
The feature delivery has been great and we are getting pretty close to not needing to do anything but update the jdk to move forward.
Now, if only I could get devs to stop using lombok....
throwaway92422 2 days ago [-]
I like Lombok. The best annotation is @Builder. It makes working with records much more convenient.
The upsides are:
- it generates code and does not do anything funky with internals,
- it has a lot of knobs if you need something a little different.
The downside is that it does not provide you with the other Lombok annotations. In practice that has been OK!
cogman10 1 days ago [-]
Besides this I'd also point to Immutables and AutoValue which both can make builders without touching jvm internals.
xxs 1 days ago [-]
>Now, if only I could get devs to stop using lombok....
that's actually not too hard. It has happened to get them all converted to avid opponents of lombok.
sgammon 2 days ago [-]
> Now, if only I could get devs to stop using lombok
One of the only remaining reasons I discard libraries without a further thought
pests 2 days ago [-]
Been a long time since I used Java. I checked out lombok since you mentioned it. Does it really just internally create all those methods without there being actual source code? It seems nice but really spooky-action-at-a-distance feeling. Sounds like a nightmare.
elchananHaas 2 days ago [-]
Yes, from a language design it's ugly and the implementation is convoluted. From a user perspective it's awesome and enables better interfaces.
pests 2 days ago [-]
I don't even agree with their demo video. It shows the "hard" way of autogenerating settings/getters, toString, hash, etc. The end result was like an additional ~80 boilerplate lines. I have no problem keeping those lines around.. opposed to adding the lombak.jar and changing my build config. I do understand the user perspective of it "just working" and of course the getters/setters will grow as you add fields... it just seems like such a low amount of code to keep around it shouldnt be too much hassle.
lucumo 1 days ago [-]
The problem isn't the first-time generating the code. The problem is when objects gain fields and people forget to add them to hashCode and equals (or worse, hashCode OR equals). It's the kind of thing you won't notice until months later when you have intermittent hard to debug glitches in your system.
Records have reduced the advantages of Lombok by a boatload. But there still are some things that can't be records.
xxs 1 days ago [-]
>add them to hashCode and equals (or worse, hashCode OR equals)
That's a fundamental misunderstanding of hashCode, and lombok makes no exception. Not all fields need to be used to calculate hashCode, it makes the overall performance worse in most cases. Equals is of course different, however if you have an identity (i.e. database primary key), only the identity should be used.
lucumo 10 hours ago [-]
> That's a fundamental misunderstanding of hashCode
Right. So the parenthetical clause should more correctly state "(or worse, adding them to hashCode but not equals)". That's fine, but it's still the same problem: there's a hidden dependency between changes in two or more locations. People make errors in those kind of updates all the time.
cogman10 1 days ago [-]
Hurray! I thought I was the only one that understood this. There are two of us!
I've seen so many performance issues with hashcode because devs will put all fields into it. Even though there's an id column or even fields that imply other fields. Hashing 1000 char strings when there's a UUID or int field that guarantees identity is silly.
I think it's because devs have an preference for symmetry. I see the same thing happen when they preferably add setters for all fields even though they aren't necessary.
xxs 1 days ago [-]
>There are two of us!
Realistically, I have trained quite a few folks on my own.
>I think it's because devs have an preference for symmetry.
Another option is that's the default for all IDEs auto gen, so few clicks/taps and it's done.
ivan_gammel 1 days ago [-]
In Idea you have to explicitly select the fields which should be included. So it’s always choice of a developer to misuse it.
My favorite question on interviews is explaining all methods of class Object, including the contract and best practices for equals/hashCode. Failure to answer this question automatically disqualifies applicants to mid-level and senior positions.
xxs 1 days ago [-]
>all methods of class Object
finalize() is actually is a very hard mode; I'd not expect any extra senior to be able to explain it properly (incl. the semantics of JMM, the fact half created objects can be finalized; the resurrection ability). Deprecated now, so perhaps no need?
wait/notify/notifyAll - easier, still require some practice, also not that useful any longer; but still I'd expect to know not to use a naked notify and how to properly use a loop around wait
clone() - it'd be a hard nut for many, and I have seen more than enough implementations that straight out use new XXX(); not very difficult but not intuitive
hashCode/equals -> hashCode being by default a random number generator is sort of cool; yes they are the backbone of all collection framework; also the value of the not overridden hashCode() is available through System.identifyHashCode()
getClass() - if included anonymous classes, it might puzzle some
toString() - finally something easy
---
flip note: the standard templates for intellij could use some work when it comes to the quality of hashCode;
ivan_gammel 22 hours ago [-]
> finalize() is actually is a very hard mode;
> Deprecated now, so perhaps no need?
Yes. Worth mentioning existence and “do not touch it”, but no need to go deep. Same with clone. The point of this exercise is to demonstrate that you can use the core library without shooting yourself in the foot. As for wait/notify/notifyAll, I’d expect the correct usage patterns from mid-level.
vbezhenar 1 days ago [-]
It's not 80 lines. It's many thousands of lines in any real project. Lines that you should keep synchronised with "source of truth". People love Lombok for a reason. And most developers don't really care about implementation details, they believe that Lombok is "big enough" to work in the foreseeable future. It worked for them for years, it's supported well in Idea.
I, personally, avoid Lombok, specifically because I care about implementation details. Because if I would have wanted better Java, I'd go with Kotlin rather than this hacky way of using another Java-like language. But other people hold different opinions.
xxs 1 days ago [-]
>It's many thousands of lines in any real project.
I dont use lombok and the latter has been actively removed. If you have access to records, lombok is just bad. Even if you dont have - public final fields are sufficient in most case + c-tor and validation there. Just dont use getters & setters.
ivan_gammel 1 days ago [-]
For me personally those thousands of lines are often garbage. For example, constructors and fields generated by Lombok do not validate inputs, yet failing early instead of deferring the problem to client code reading the value is the easiest way to fight bugs. Sometimes one business method setting several values instead of multiple getters is better or the only possible way to preserve integrity of the state. Sometimes a field reflecting internal state will be annotated to produce getter and thus breaking the encapsulation. With all of that Lombok encourages poor code quality by enabling developers being lazy.
cogman10 1 days ago [-]
There are alternatives that don't rely on jvm internals to accomplish their goals. They do like 90% of what lombok does. Immutables and AutoValue are two examples I've used that work fine.
The 10% lombok can do because it's peaking at internals isn't valuable. I don't, for example, need an annotation to create a static logger. That's dumb.
xxs 1 days ago [-]
>"just working" and of course the getters/setters will grow as you add fields...
The issue is having plainly useless getters and setter (just use public fields); along with the fact that mutable hashCode/equals
saghm 1 days ago [-]
I mean, we're talking about how the best way to externally modify things that are ostensibly supposed to be internal and immutable, right? I feel like pretty much any way of doing it is going to be spooky action at a distance by definition. Modifying private internals from the outside isn't something you can do with mundane-action-at-close-range or whatever the alternative would otherwise be called.
pandemic_region 2 days ago [-]
Why the Lombok hate? It's just an innocent preprocessor allowing a bit of syntactic sugar right?
Other tools and libraries generally do not interact in such an errorprone manner.
That said, when you know how it works, what it needs, and you know how to iron all of those tiny wrinkles, it works fine and saves you some code and/or sanity. It's not the devil, it's a powerful tool with some downsides.
pandemic_region 1 days ago [-]
I did not know about ErrorProne thanks, I can see this is useful indeed and then Lombok would be the odd one out breaking that tool. About the special IDE plugin, IntelliJ has native support but yeah it needs a bit of attention that's true. Overall the minor extra hassle is still worth it in my world. We're not using all of the annotations, but (in order of utility) @AllArgsConstructor, @Data, @Getter, @Setter and the occasional @Synchronized.
gf000 1 days ago [-]
The biggest gripe is that it is not a preprocessor. Java has standard interfaces to properly preprocess code, and they have a very thoughtful limitation of no code modification, it is strictly additive.
There are very cool libraries making use of it, e.g. mapstruct.
Now, lombok in its current approach can't make use of it, as it explicitly wants to modify the given class with its annotations. This is forbidden, so they literally hack into the java compiler classes themselves, modifying the AST in-flight. As you can imagine, this might break at a new Java update any time.
cogman10 1 days ago [-]
> As you can imagine, this might break at a new Java update any time.
It's not a might. If you look at the lombok changelog they have a release for nearly every new jdk version because they break constantly.
When a project can't move up jvm versions it's very frequently been because of lombok.
And if you look at the commit log, it's all a single dev running the show. He's been doing it for years which means the stability of your project is pretty dependant on this one guy keeping things up to date and finding work arounds to get at the jvm internals.
There's one of these for just about every occasion
pests 2 days ago [-]
It feels weird to me that functions exist without being in the source code.
pandemic_region 2 days ago [-]
I never looked at it that way, but I get now where you're coming from. To me, the Lombok annotations are more like @Transactional, providing some useful boilerplate that I don't need to care about, it just works.
pests 2 days ago [-]
I agree its extremely useful. I am just used to languages/frameworks that at least autogenerate the code. Maybe some inheritance to give you a place to make changes that wont be wiped.
dingi 2 days ago [-]
It’s not an innocent preprocessor. It’s a hack which breaks on almost every JDK upgrade.
mschuster91 1 days ago [-]
Now, given that a looot of projects use Lombok, it might be worth a try to actually standardize (or at least upstream) the feature given there seems to be an obvious demand of people to not having to pollute their codebase with tons of pointless getter and setter boilerplate.
samus 10 hours ago [-]
Nobody requires projects to pollute their codebases with getter and setter boilerplate. People do that to themselves. The OpenJDK project should not enable that nonsense further, but provide support for better programming patterns. Support for creating getters and setters is already provided by annotation processors.
Official support for compiler plugins (Lombok is an unofficial compiler plugin, not a mere annotation processor!) would effectively allow people to create dialects of Java. This would splinter and blunt the momentum that has carried the language forward for the last 30 years by fragmenting the ecosystem because for many legacy applications it will be impossible to migrate to one of the prevalent dialects.
cogman10 1 days ago [-]
AutoValue, Immutables, MapStruct. All libraries that do what Lombok does without using JVM internals.
Records also literally do a huge portion of what Lombok does.
There are a lot of fairly popular alternatives to Lombok out there that don't need a constant eye on maintenance. Lombok is probably the most popular but that's really mostly because it's got the first mover advantage.
Supermancho 16 hours ago [-]
> First, it's not a "crusade" but the steps necessary to deliver the features Java's users demand
Semantics. Nobody demanded anything, if we want to play word games.
The java philosophy of final, makes software less extensible. This is the point; to have less overriding. Regardless of what the voting body decides (the meaning of users being subtly repurposed), the feature is anti-developer-agency past the point of healthy balance. I dont understand the enthusiasm.
pron 7 hours ago [-]
> Nobody demanded anything, if we want to play word games.
People asked for lightweight concurrency, better FFI, faster startup, value classes etc.. How could we have done any of that without the integrity work? It's like saying, nobody asked you to make noise and dust to lay down cable ducts, we just asked for fast internet.
> The java philosophy of final, makes software less extensible. This is the point; to have less overriding. Regardless of what the voting body decides (the meaning of users being subtly repurposed), the feature is anti-developer-agency past the point of healthy balance.
There's absolutely no capability that this JEP removes (also, you seem to be confusing final classes/methods with final fields). All it does is say that the minority of programs that want to mutate final fields just have to tell the JVM about it so that it won't apply some future optimisation. Nothing is being taken away.
The JEP is: if you're mutating finals, you can continue to do so; if you're not -- you'll eventually get better performance. What's not to like?
samus 10 hours ago [-]
Final making software less extensive is a red herring. The JEP is about final for fields, not final for classes. Nothing changes regarding the latter. It merely deprecates facilities that enable programmers to ignore the explicitly stated intentions of other developers.
Supermancho 4 hours ago [-]
> enable programmers to ignore the explicitly stated intentions of other developers
You say ignore. This is unnecessarily pejorative. You mean "change".
Software does not care about explicit intentions or who is leveraging it. Overriding is one of the essential methods of extending software.
pron 3 hours ago [-]
Java allows you to freely change any decision made by any author of any library you use. None of that ability has been taken away.
However, changing other code (and potentially changing the assumptions on which it was built) shouldn't be as easy as using it in a way that preserves its assumptions. If it were just as easy, you could accidentally break other code's assumptions.
In some situations, all that's required of you is merely to declare that you indeed intend to change some other code's assumptions; in more involved situations (e.g. a library method computes the sum of its argument and you want to change it to compute the product) the work is more involved, but you can still do it relatively easily.
What we have blocked is the ability of a library to change the assumptions of another library or of the application's without the application knowing about it. A library absolutely must not be allowed to do that while hiding that from the application.
twic 2 days ago [-]
Not in my experience. I run my applications with default integrity, and when i hit a problem, i find out what caused it, and fix it. Often it's just a matter of upgrading a library to a newer version which doesn't do naughty reflective things, or changing some config to stop it doing so, or changing our own code. We had a serialisation library which did deep reflection to be slightly more efficient at serialising BigDecimal; there was a system property to turn that off, so i set it. We had code doing deep reflection into JMX to get the current PID; i changed it to use ProcessHandle instead. My colleague wrote a little test data library which does some horrific things; i might delete it and adopt Instacio instead.
I think there's only one case where i ended up relaxing integrity, and i'm hoping that's temporary - it will take more time to fix than i was willing to spend.
sgammon 2 days ago [-]
It's 2025 and Guava supports JPMS now
Phelinofist 2 days ago [-]
Android doesn't :(
sgammon 2 days ago [-]
One could easily generate the configurations via annotations, and I imagine Micronaut will do just that
2 days ago [-]
elric 2 days ago [-]
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.
exabrial 2 days ago [-]
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 2 days ago [-]
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.
dstine 2 days ago [-]
In this 2014 talk, Brian shows a slide in which he characterizes a visible fraction of his job as “regretting serialization” (somewhat tongue-in-cheek).
It's not just Java, either. The original .NET serialization system is very obviously inspired by Java, and has many of the same issues - to the point where it's effectively deprecated these days.
exabrial 2 days ago [-]
Serialization is unfortunately important though... want to suspend a program and resume it later, or transfer it over a network, etc. The real world is kinda a let down.
esprehn 2 days ago [-]
That's a hydration problem though, it doesn't require magically serializing the running state like java Serializable wants to. It just needs a way to produce minimal inputs to then reconstitute the state again on the other side.
magicalhippo 2 days ago [-]
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.
theanonymousone 2 days ago [-]
If there was a r/nottheonion for tech :D
Jokes aside, I thought the ability to mutate final fields was already removed/restricted after Java 17 :/
bironran 2 days ago [-]
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.
pron 2 days ago [-]
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.
LadyCailin 2 days ago [-]
Would be nice to have a single “--test-mode” flag that is only meant to be set when running tests, and allows for all this leniency, (add opens, etc) in a single flag.
pron 2 days ago [-]
We should separate the problem from the solution. The problem is that running tests may require relatively many integrity-busting flags. That is true.
There are, however, better solutions than a global test-mode flag that, invariably, will be used by some in production out of laziness, leaving no auditable record of what integrity constraints need to be violated and why. When a new team lead is appointed some years later they will have a hard time trying to reduce the entropy.
The better solutions will arrive in due course, but until then, build tools can automatically add most of the necessary flags. They should be encouraged to do that.
LadyCailin 2 days ago [-]
So make the flag remove some other feature, which is critical to production, like the ability to run main() or something.
On the other hand, I don’t think the solution to someone holding a shotgun to their foot and threatening to pull the trigger is to make everyone wear armored shoes. They’re already a lost cause, and there are a billion other ways they can shoot their foot off, if they are so inclined.
I agree with the principal of making it hard to screw things up assuming good faith efforts (making it hard to fall in the pit of despair), so overall I like the JEP.
pron 2 days ago [-]
> On the other hand, I don’t think the solution to someone holding a shotgun to their foot and threatening to pull the trigger is to make everyone wear armored shoes.
I don't think so, either, it's just that I think there are better solutions than a test-mode flag at the level of the `java` launcher. If the mechanism that runs the tests can automatically configure the appropriate capabilities without requiring the user running the tests to do manual configuration then the problem is solved for those who just want to easily run tests just as well as a test-mode configuration.
The idea of a test-mode flag has been floated before and considered; we're not ruling it out, but if such a mode is ever added, I can't tell you now what it would mean exactly. In any event, it's better to carefully study the nature of the problem and its origins before suggesting a particular solution. As Brian Goetz likes to say, today's solutions may well become tomorrow's problems.
> They’re already a lost cause, and there are a billion other ways they can shoot their foot off, if they are so inclined.
True, but our experience shows that it's not a good idea to make the bad choice the easiest one, or people may pick it out of laziness. Let those who want to shoot themselves in the foot work for it. If nothing else, it increases the chance that they learn what their (not-entirely-trivial) configuration means, and maybe they'll realise they don't want it after all.
Someone might point out that there are still ways to do the wrong thing out of laziness by blindingly copying a configuration from StackOverflow etc., but we're not done yet.
PathOfEclipse 2 days ago [-]
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 2 days ago [-]
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?
steveklabnik 2 days ago [-]
> 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.
cesarb 2 days ago [-]
> The JEP says: [...]
The JEP also says:
> The sun.reflect.ReflectionFactory class only supports deserialization of objects whose classes implement java.io.Serializable.
In my experience, most classes being deserialized by libraries like GSON do not implement Serializable. Implementing Serializable is mostly done by classes which want to be serialized and deserialized through Java's native serialization format (which is used by nothing outside Java, unlike cross-platform formats like JSON or CBOR).
vips7L 2 days ago [-]
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 2 days ago [-]
all state is immutable :) a change creates new state - which is immutable
vips7L 2 days ago [-]
:) no its not.
bdangubic 2 days ago [-]
if you change the state, it is not same state, it is a new state
twic 2 days ago [-]
It strikes me that we could have a way to reflectively create an object from values for all its fields in a single step - similar to what record constructor does, but for any class (could even be Class::getCanonicalConstructor, returning a java.lang.reflect.Constructor). It would be equivalent to creating an uninitialised instance and then setting its fields one by one, but the partly-initialised object would never be visible. This should probably be restricted, because it bypasses any invariants the constructor enforces, but as you say, ultimately serialisation libraries do need to do that.
recursivecaveat 2 days ago [-]
I don't know if Java serialization supports this kind of thing, but if you have object A has a pointer to object B and vice-versa, there's no order to deserialize them without passing through a partially-initialized state that preserves the object identity relationship. I suppose you can't construct this kind of loopy references graph with final fields without reflection in the first place, so it's kindof chicken and egg. For the very common case of DAG-shaped data or formats that don't support references I think the one-shot internal constructor works though.
n_plus_1_acc 2 days ago [-]
Just do it like Rust
merb 2 days ago [-]
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)
gf000 11 hours ago [-]
Gradle is a general build tool, while cargo/go are only for their respective languages.
The moment you need to run some code from another language on your code to generate some other code or whatever, they break down, while Gradle can be used for pretty much anything.
In other words, cargo/go only solve the cache/parallelize/resolve task dependencies problem for "hard coded" special cases, the moment you strive away from that you are in a world of pain.
PaulHoule 2 days ago [-]
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 2 days ago [-]
private fields are no issue
nightpool 2 days ago [-]
[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
pron 2 days ago [-]
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 2 days ago [-]
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.
I suppose serializing the JVM state itself to avoid the cold start problem might take advantage of this?
nightpool 2 days ago [-]
Why would that prevent the JVM from using the same speculative optimization JIT with deoptimization hooks approach?
Misdicorl 2 days ago [-]
It wouldn't, but it might preclude using (future) optimizations that forgo those de-optimization hooks?
GauntletWizard 2 days ago [-]
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 2 days ago [-]
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.
This approach is surprisingly (or unsurprisingly) one of the most robust.
jjmarr 2 days ago [-]
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.
PhilipRoman 2 days ago [-]
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.
int_19h 19 hours ago [-]
The real problem is that "const" in C/C++ is a misnomer - the literal meaning of the word means "unchanging", and yet if you have a pointer or reference to a const T, it can, in fact, change. A "const pointer" in C is really a read-only pointer to some data that may or may not be actually constant.
In C, at least, you can usually distinguish a true pointer to immutable data by using `restrict`.
mb7733 2 days ago [-]
> 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 2 days ago [-]
(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 2 days ago [-]
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.
aardvark179 2 days ago [-]
So happy to see this, thank you Ron and Alan.
satyanash 2 days ago [-]
> Prepare to make final mean final
missed opportunity to call it "final final"
TOGoS 2 days ago [-]
> 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.
immibis 10 hours ago [-]
I don't see the point in putting another guard rail behind the override that's supposed to remove all guard rails. Java doesn't even have a security model any more, so what are you protecting?
Programmers from their own mistakes? That's fine, but this is about cases where they set the "I really mean it and this isn't a mistake" flag.
The JVM's optimized code from programmers? Plausible, but aren't there already many cases where things get deoptimized based on run-time state changes?
It feels like someone just said "final means final!" without really thinking about the purpose of a JVM. Will there be a proposal to enforce checked exceptions, too?
samus 10 hours ago [-]
> Java doesn't even have a security model any more, so what are you protecting?
That security model was obsolete and didn't fulfill its original purpose anymore. Most applications have never enabled it.
> Programmers from their own mistakes? That's fine, but this is about cases where they set the "I really mean it and this isn't a mistake" flag.
Not at all, the vast majority of code using this feature is part of libraries. Application developers are often not aware that this even happens. Anyway, it will also in the future be possible for developers to shoot themselves in the foot with this feature if they really want. They just have to work a bit harder for it.
> The JVM's optimized code from programmers? Plausible, but aren't there already many cases where things get deoptimized based on run-time state changes?
This is not a good thing because interpreted mode is terribly slow. The JIT exists to make Java fast, and developers should not made this harder than necessary.
> It feels like someone just said "final means final!" without really thinking about the purpose of a JVM. Will there be a proposal to enforce checked exceptions, too?
Do people really write `final` and are OK being aware that the field's value could still change after all?
Checked exceptions are already enforced. If you mean changing all exceptions to be checked: no, that would be a catastrophic backwards compatibility break, with no good upside.
immibis 8 hours ago [-]
> Do people really write `final` and are OK being aware that the field's value could still change after all?
The converse: people really write `field.setAccessible(true)` and are OK being aware that they are breaking another programmer's assumptions. That's the point of `field.setAccessible(true)` existing at all. It's monkey-patching.
Otherwise you might as well just delete the method and throw a `NoSuchMethodError` when someone calls it - nobody is calling `setAccessible(false)`
I have some experience hacking on Java Minecraft. Minecraft's code is what it is - take it or leave it. The more stuff you can do without modifying that code, the simpler the overall system is. If you can set a field with reflection then you don't need to modify that class to make the field public or non-final. In other words, monkey-patching. (Newer frameworks support more modification to Minecraft's own code with less effort, though.)
> This is not a good thing because interpreted mode is terribly slow. The JIT exists to make Java fast, and developers should not made this harder than necessary.
And it does that by being smart. It makes assumptions, compiles code given those assumptions, and recompiles it if they actually do not hold. For example, I expect that a method with a List parameter is most likely only ever called with one implementation of List, and the JVM knows this and will compile the method as if the parameter is ArrayList, and will compile the method for the specific implementation it sees in the first few calls, with a bail-out check forcing the method to be recompiled if it's ever not that implementation.
And if the JVM never sees a subclass of a certain class, it can statically dispatch all calls to references of that type, as if the class were final, even if it isn't marked final.
And, likewise, if the JVM never sees a write instruction to a certain field, except once in the constructor, it should be able to treat it as if the field were final, and avoid reloading it after method calls. If it loads a class with an instruction that writes that field, it has to recompile all those methods.
None of this should be news to the JVM engineers.
pjmlp 8 hours ago [-]
Yes it does, all of this is to make Java fully safe by default, in the age of cybersecurity laws, hence all loopholes are being closed down, in reflection, JNI and Panama.
The security model was deprected, just like .NET dropped CAS in .NET Core, because it wasn't sound, and without applets, the OS security model was the right way.
mberning 2 days ago [-]
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.
stickfigure 2 days ago [-]
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 2 days ago [-]
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 2 days ago [-]
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!)
recursive 2 days ago [-]
> 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 2 days ago [-]
> 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 2 days ago [-]
Exactly. And for function parameters, rust also (usually) makes it obvious when you’re passing by value, by reference or by 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 2 days ago [-]
Fair point. I guess this is the cost of dynamic languages.
mont_tag 2 days ago [-]
> 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 2 days ago [-]
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.
titzer 2 days ago [-]
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 2 days ago [-]
> 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.
pjmlp 8 hours ago [-]
D goes one step further, as const is transitive.
nirvdrum 2 days ago [-]
I'd be happy with a `val` keyword. I thought that was a seriously missed opportunity when they introduced `var`.
xxs 2 days ago [-]
>but please come up with something to discourage mutability)
Records?
duskwuff 2 days ago [-]
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)
throwaway92422 2 days ago [-]
Records in Java have final fields.
neonsunset 2 days ago [-]
.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).
Traubenfuchs 2 days ago [-]
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 2 days ago [-]
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.
xxs 2 days ago [-]
Around 19 year late, still better than never.
ars 2 days ago [-]
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.
pron 1 days ago [-]
No, this JEP only talks about fields; it has no impact whatsoever on locals. There's no positive or negative impact on performance when making locals final or not.
keybored 2 days ago [-]
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 2 days ago [-]
> --illegal-final-final-mutation=deny will result in Field::set throwing an IllegalAccessException for every illegal final field mutation.
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,
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.The problem with these various "integrity by default" options is that, in most cases, granting access to one effectively grants access to all. For instance, JNI, agent libraries, and JPMS options can each be used to bypass restrictions, making the separation between them largely illusory. Integrity, as framed here, is ultimately binary.
The unfortunate reality of the "integrity by default" crusade is that applications relying on libraries and tools that modify internals will continue to do so. The JDK hasn’t filled any gaps—it has only made an already delicate situation worse.
I think Java's handling of this transition compares very favourably to how other languages have handled similar transitions from some old model to a new one (or evolution in general) in terms of balancing the needs of both old and new projects.
8->11 was really a pain in the neck. 11->17 had some pain, but mostly was nothing serious.
17->21 has been painless.
And I have some projects running on 24 already with no problems.
The feature delivery has been great and we are getting pretty close to not needing to do anything but update the jdk to move forward.
Now, if only I could get devs to stop using lombok....
The upsides are: - it generates code and does not do anything funky with internals, - it has a lot of knobs if you need something a little different.
The downside is that it does not provide you with the other Lombok annotations. In practice that has been OK!
that's actually not too hard. It has happened to get them all converted to avid opponents of lombok.
One of the only remaining reasons I discard libraries without a further thought
Records have reduced the advantages of Lombok by a boatload. But there still are some things that can't be records.
That's a fundamental misunderstanding of hashCode, and lombok makes no exception. Not all fields need to be used to calculate hashCode, it makes the overall performance worse in most cases. Equals is of course different, however if you have an identity (i.e. database primary key), only the identity should be used.
Right. So the parenthetical clause should more correctly state "(or worse, adding them to hashCode but not equals)". That's fine, but it's still the same problem: there's a hidden dependency between changes in two or more locations. People make errors in those kind of updates all the time.
I've seen so many performance issues with hashcode because devs will put all fields into it. Even though there's an id column or even fields that imply other fields. Hashing 1000 char strings when there's a UUID or int field that guarantees identity is silly.
I think it's because devs have an preference for symmetry. I see the same thing happen when they preferably add setters for all fields even though they aren't necessary.
Realistically, I have trained quite a few folks on my own.
>I think it's because devs have an preference for symmetry.
Another option is that's the default for all IDEs auto gen, so few clicks/taps and it's done.
My favorite question on interviews is explaining all methods of class Object, including the contract and best practices for equals/hashCode. Failure to answer this question automatically disqualifies applicants to mid-level and senior positions.
flip note: the standard templates for intellij could use some work when it comes to the quality of hashCode;
> Deprecated now, so perhaps no need?
Yes. Worth mentioning existence and “do not touch it”, but no need to go deep. Same with clone. The point of this exercise is to demonstrate that you can use the core library without shooting yourself in the foot. As for wait/notify/notifyAll, I’d expect the correct usage patterns from mid-level.
I, personally, avoid Lombok, specifically because I care about implementation details. Because if I would have wanted better Java, I'd go with Kotlin rather than this hacky way of using another Java-like language. But other people hold different opinions.
I dont use lombok and the latter has been actively removed. If you have access to records, lombok is just bad. Even if you dont have - public final fields are sufficient in most case + c-tor and validation there. Just dont use getters & setters.
The 10% lombok can do because it's peaking at internals isn't valuable. I don't, for example, need an annotation to create a static logger. That's dumb.
The issue is having plainly useless getters and setter (just use public fields); along with the fact that mutable hashCode/equals
Other tools and libraries generally do not interact in such an errorprone manner.
That said, when you know how it works, what it needs, and you know how to iron all of those tiny wrinkles, it works fine and saves you some code and/or sanity. It's not the devil, it's a powerful tool with some downsides.
There are very cool libraries making use of it, e.g. mapstruct.
Now, lombok in its current approach can't make use of it, as it explicitly wants to modify the given class with its annotations. This is forbidden, so they literally hack into the java compiler classes themselves, modifying the AST in-flight. As you can imagine, this might break at a new Java update any time.
It's not a might. If you look at the lombok changelog they have a release for nearly every new jdk version because they break constantly.
When a project can't move up jvm versions it's very frequently been because of lombok.
And if you look at the commit log, it's all a single dev running the show. He's been doing it for years which means the stability of your project is pretty dependant on this one guy keeping things up to date and finding work arounds to get at the jvm internals.
There's one of these for just about every occasion
Official support for compiler plugins (Lombok is an unofficial compiler plugin, not a mere annotation processor!) would effectively allow people to create dialects of Java. This would splinter and blunt the momentum that has carried the language forward for the last 30 years by fragmenting the ecosystem because for many legacy applications it will be impossible to migrate to one of the prevalent dialects.
Records also literally do a huge portion of what Lombok does.
There are a lot of fairly popular alternatives to Lombok out there that don't need a constant eye on maintenance. Lombok is probably the most popular but that's really mostly because it's got the first mover advantage.
Semantics. Nobody demanded anything, if we want to play word games.
The java philosophy of final, makes software less extensible. This is the point; to have less overriding. Regardless of what the voting body decides (the meaning of users being subtly repurposed), the feature is anti-developer-agency past the point of healthy balance. I dont understand the enthusiasm.
People asked for lightweight concurrency, better FFI, faster startup, value classes etc.. How could we have done any of that without the integrity work? It's like saying, nobody asked you to make noise and dust to lay down cable ducts, we just asked for fast internet.
> The java philosophy of final, makes software less extensible. This is the point; to have less overriding. Regardless of what the voting body decides (the meaning of users being subtly repurposed), the feature is anti-developer-agency past the point of healthy balance.
There's absolutely no capability that this JEP removes (also, you seem to be confusing final classes/methods with final fields). All it does is say that the minority of programs that want to mutate final fields just have to tell the JVM about it so that it won't apply some future optimisation. Nothing is being taken away.
The JEP is: if you're mutating finals, you can continue to do so; if you're not -- you'll eventually get better performance. What's not to like?
You say ignore. This is unnecessarily pejorative. You mean "change".
Software does not care about explicit intentions or who is leveraging it. Overriding is one of the essential methods of extending software.
However, changing other code (and potentially changing the assumptions on which it was built) shouldn't be as easy as using it in a way that preserves its assumptions. If it were just as easy, you could accidentally break other code's assumptions.
In some situations, all that's required of you is merely to declare that you indeed intend to change some other code's assumptions; in more involved situations (e.g. a library method computes the sum of its argument and you want to change it to compute the product) the work is more involved, but you can still do it relatively easily.
What we have blocked is the ability of a library to change the assumptions of another library or of the application's without the application knowing about it. A library absolutely must not be allowed to do that while hiding that from the application.
I think there's only one case where i ended up relaxing integrity, and i'm hoping that's temporary - it will take more time to fix than i was willing to spend.
Sounds like a good evolution to me.
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.
So I think it's safe to say "what about serialization?" is always going to be asked.
https://www.youtube.com/watch?v=2y5Pv4yN0b0&t=930s
Link is to the start of a sequence of three slides, the third of which is the slide in question.
For a more recent update on serialization, watch this talk “Serialization: A New Hope”: https://www.youtube.com/watch?v=mIbA2ymCWDs
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.
Jokes aside, I thought the ability to mutate final fields was already removed/restricted after Java 17 :/
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.
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.
There are, however, better solutions than a global test-mode flag that, invariably, will be used by some in production out of laziness, leaving no auditable record of what integrity constraints need to be violated and why. When a new team lead is appointed some years later they will have a hard time trying to reduce the entropy.
The better solutions will arrive in due course, but until then, build tools can automatically add most of the necessary flags. They should be encouraged to do that.
On the other hand, I don’t think the solution to someone holding a shotgun to their foot and threatening to pull the trigger is to make everyone wear armored shoes. They’re already a lost cause, and there are a billion other ways they can shoot their foot off, if they are so inclined.
I agree with the principal of making it hard to screw things up assuming good faith efforts (making it hard to fall in the pit of despair), so overall I like the JEP.
I don't think so, either, it's just that I think there are better solutions than a test-mode flag at the level of the `java` launcher. If the mechanism that runs the tests can automatically configure the appropriate capabilities without requiring the user running the tests to do manual configuration then the problem is solved for those who just want to easily run tests just as well as a test-mode configuration.
The idea of a test-mode flag has been floated before and considered; we're not ruling it out, but if such a mode is ever added, I can't tell you now what it would mean exactly. In any event, it's better to carefully study the nature of the problem and its origins before suggesting a particular solution. As Brian Goetz likes to say, today's solutions may well become tomorrow's problems.
> They’re already a lost cause, and there are a billion other ways they can shoot their foot off, if they are so inclined.
True, but our experience shows that it's not a good idea to make the bad choice the easiest one, or people may pick it out of laziness. Let those who want to shoot themselves in the foot work for it. If nothing else, it increases the chance that they learn what their (not-entirely-trivial) configuration means, and maybe they'll realise they don't want it after all.
Someone might point out that there are still ways to do the wrong thing out of laziness by blindingly copying a configuration from StackOverflow etc., but we're not done yet.
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?
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.
The JEP also says:
> The sun.reflect.ReflectionFactory class only supports deserialization of objects whose classes implement java.io.Serializable.
In my experience, most classes being deserialized by libraries like GSON do not implement Serializable. Implementing Serializable is mostly done by classes which want to be serialized and deserialized through Java's native serialization format (which is used by nothing outside Java, unlike cross-platform formats like JSON or CBOR).
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.
The moment you need to run some code from another language on your code to generate some other code or whatever, they break down, while Gradle can be used for pretty much anything.
In other words, cargo/go only solve the cache/parallelize/resolve task dependencies problem for "hard coded" special cases, the moment you strive away from that you are in a world of pain.
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...
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.
> 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
In C, at least, you can usually distinguish a true pointer to immutable data by using `restrict`.
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?
If I understand correctly, here's a C++ example that has undefined behavior:
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: 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.missed opportunity to call it "final final"
/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.
Programmers from their own mistakes? That's fine, but this is about cases where they set the "I really mean it and this isn't a mistake" flag.
The JVM's optimized code from programmers? Plausible, but aren't there already many cases where things get deoptimized based on run-time state changes?
It feels like someone just said "final means final!" without really thinking about the purpose of a JVM. Will there be a proposal to enforce checked exceptions, too?
That security model was obsolete and didn't fulfill its original purpose anymore. Most applications have never enabled it.
> Programmers from their own mistakes? That's fine, but this is about cases where they set the "I really mean it and this isn't a mistake" flag.
Not at all, the vast majority of code using this feature is part of libraries. Application developers are often not aware that this even happens. Anyway, it will also in the future be possible for developers to shoot themselves in the foot with this feature if they really want. They just have to work a bit harder for it.
> The JVM's optimized code from programmers? Plausible, but aren't there already many cases where things get deoptimized based on run-time state changes?
This is not a good thing because interpreted mode is terribly slow. The JIT exists to make Java fast, and developers should not made this harder than necessary.
> It feels like someone just said "final means final!" without really thinking about the purpose of a JVM. Will there be a proposal to enforce checked exceptions, too?
Do people really write `final` and are OK being aware that the field's value could still change after all?
Checked exceptions are already enforced. If you mean changing all exceptions to be checked: no, that would be a catastrophic backwards compatibility break, with no good upside.
The converse: people really write `field.setAccessible(true)` and are OK being aware that they are breaking another programmer's assumptions. That's the point of `field.setAccessible(true)` existing at all. It's monkey-patching.
Otherwise you might as well just delete the method and throw a `NoSuchMethodError` when someone calls it - nobody is calling `setAccessible(false)`
I have some experience hacking on Java Minecraft. Minecraft's code is what it is - take it or leave it. The more stuff you can do without modifying that code, the simpler the overall system is. If you can set a field with reflection then you don't need to modify that class to make the field public or non-final. In other words, monkey-patching. (Newer frameworks support more modification to Minecraft's own code with less effort, though.)
> This is not a good thing because interpreted mode is terribly slow. The JIT exists to make Java fast, and developers should not made this harder than necessary.
And it does that by being smart. It makes assumptions, compiles code given those assumptions, and recompiles it if they actually do not hold. For example, I expect that a method with a List parameter is most likely only ever called with one implementation of List, and the JVM knows this and will compile the method as if the parameter is ArrayList, and will compile the method for the specific implementation it sees in the first few calls, with a bail-out check forcing the method to be recompiled if it's ever not that implementation.
And if the JVM never sees a subclass of a certain class, it can statically dispatch all calls to references of that type, as if the class were final, even if it isn't marked final.
And, likewise, if the JVM never sees a write instruction to a certain field, except once in the constructor, it should be able to treat it as if the field were final, and avoid reloading it after method calls. If it loads a class with an instruction that writes that field, it has to recompile all those methods.
None of this should be news to the JVM engineers.
The security model was deprected, just like .NET dropped CAS in .NET Core, because it wasn't sound, and without applets, the OS security model was the right way.
(yes yes, I know, that would break syntax... but please come up with something to discourage mutability)
Yeah I know there's ways around it, but then the author known what they told the other party to expect.
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!)
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.
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
That `&mut self` lets you know that it's going to mutate.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.
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.
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.
That's like using a hammer on a screw, clearly not the right way.
Thankfully I've never worked on such codebases.
Records?
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.
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.
Seems like this way?