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.
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.
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.
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.
> 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.
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.
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.
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.
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.
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.
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.
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.
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.
>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.
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.
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.
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;
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 hope they do know better, it's actually rather poor. 1st it should not be used for a single parameter - just use hashCode() directly and ensure non-null in the c-tor (or Objects.hashCode). Then, in general you need - only few fields to calc a decent high cardinality hashCode...
Last: 31 as a constant (used in most mul/add schemas by java). It's a single byte prime, so it has some benefits. It can be implemented via shift and add (x<<5 - x) but modern CPUs implement mul quite well and don't have carry flag deps so shift+add is worse in general. However, as a constant is more like to cause collisions in a general purpose hash function. It's close to being power of 2 and multiple of 10 -> 32 and 30.
> 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.
That's proper bad, however it should be noted in a pull request and explained how hashCode operates - people learn and improve.
> there's a hidden dependency between changes in two or more ___location
True, of course. However, just do not modify hashCode and consider if the extra fields do contribute to equality either.
Realistically I have not seen this error since very early 00s. I have seen use of mutable fields in hashCode, though (and the latter being modified while added to a hashset, used as map keys. lombok encourages such designs).
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.
>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.
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.
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.
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.
> 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.
> 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?
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.
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.
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.