Should You Still Use LOMBOK in 2025?
Transcript:
To use or not to use Lombok, let's find out. Lombok's primary goal is to reduce the boiler plate in the Java code. But how exactly does it do its magic? I suggest we look first into how Lombokoperates and this will help us to better understand the risks and potential benefits of using it in the Java projects. The compilation process in Java is handled by the Java compiler class or JavaC. JavaC compiles a set of source files into a set of class files. This process generally has three stages.
First, Java C reads and parses all the source files specified on the class path into an abstract syntax tree or a abstract syntax tree is the compiler's map of your code structure. Picture your code as a tree. Each node is a meaningful construct like an if statement, a variable or an operator. The branches show how these pieces fit together. It's called abstract because it throws away all the abstract details such as parentheses or semicolons and keeps only the structural meaning that the compiler really cares about. After your code is parsed, it is organized into this tree. Therefore, later steps like type checking, optimizations and generating machine code can work with a clear structure. At the second stage, the JavaC calls the annotation processors. If any annotation processor generates new source or class files, the compilation is restarted from the first stage. This process repeats until no new files are created. At the third stage, the JavaC analyzes the resulting trees and translates them into class files. Okay, so long executes as the second stage. So when the JavaC calls the annotation processors, at this point the code is in the a form, not the bite code. And that's the window in which Lombok modifies the trees.
But here's the deal or a little important side note. The official Java specification request JSR269 does not allow the annotation processor to modify the existing trees. They can only generate new files. The public API of this specification gives you classes such as the filer to create new files, but it doesn't expose the sanctioned way to modify the existing trees. Actually, there was a proposal to allow the processor to modify the existing a but this proposal was rejected. It was a conscious decision not to support a modifications. Wait, wait, wait. But how does Lombok manage to actually modify the A trees and that's where the dark sorcery happens? Lombok runs as the usual innocent annotation processor. JavaC finds its annotation processor class on the class path and happily loads it. But this class actually belongs to the annotation processor hider class which resides in the Lombok launch package. But this is not the actual annotation processor of Lofmbok. This class starts the Lombok shadow class loader. It finds the jar file it is in and starts loading classes from this jar file. Unlike standard loaders, it doesn't look for classes with the class extension. It looks for classes ending in STL. Lombox. Instead, this is actually the namespace trick so that your IDE doesn't see the Lombok types. This shadow class loader loads the annotation processor from the Lombok core package and this is the real annotation processor that transforms your code. Okay, so we get it. Lombok works under covers but how exactly does it modify the a Lombok has handler classes like handler getter handler builder and so on.
These classes run when the matching annotation is present like a getter or a builder. handlers operate on the a nodes by inserting or rewriting nodes for methods, fields, constructors, and so on. Lombok even forces Java C to run extra processing rounds by generating dummy files and patching the Java C filer to not actually save Lombok dummy files anywhere. You can find the API for writing a handler for Java C in Lombok's source code. Handlers get the annotation and the a node and mutate the tree. Let's look at the concrete example. The cleanup annotation wraps the uh final statements in the scope in a try finally block and calls the close method or any other method in the finally and that is the a rewrite not a post compile bite code patch. As a result, the long generates some code boilerplate methods like getters, setters, constructors to string and so on or builders that create nested types and methods in the a. In summary, the parser builds the a longbook handlers see the annotations and modify the tree. The compiler continues as if you have written this code yourself. Classes come out with the boiler plate already inside. Well, looks like a perfectly legitimate schema. I really don't see how it can cause any issues.
Well, actually on the contrary, there is the cost of reducing boiler plate with Lombok in such a way. Lombok sometimes breaks on new JDK or IDE versions and that is the most important downside. Lombok talks to internal compiler classes. So if the internals were somehow changed in the newer JDK versions, it can break your code until Lombok adapts. This is visible both in the change lock with the frequent JDK update or EDA update entries and also in the issue trackers when the upstream internals change. Java specifications don't support modifying the a long lumbok does its dark sorcery meaning usage the of non-public APIs to do exactly that but this moduous operandi may increase the maintainability costs of your application or reduce the stability of J applications that rely on Lombok in the future Lombok also introduces pitfalls when used with hibernate and JPA some annotations are especially risky for instance equals and hash code annotation. Long walk by default calls the getters when it generates code for equals and hash code with JPA entities. It means that it can touch the lazy associations and as a result we will get unexpected SQL or lazy initialization exceptions. Okay, but what if you tell the Lombok to use only the ID field? Well, two new entities can have the ID that equals null and so longbox equal may say true and that will of course break the logic of set and map classes. The same goes for the hash code. If the ID is DB generated, it is null before the method persist is called but then it is nonnull. Of course, a hash based on the first value changes and so it violates the hash code contract. The two-string annotation by default the two-string conotation prints all fields on lazy relations that can lead to performance burden or again to lazy initialization exceptions. Of course, you can still use to string but all lazy associations must be excluded and that's actually additional work. So it would actually be easier to write a two-string methods yourself. The data annotation. The data annotation bundles two string equals and hash code getters setters and required arguments constructor. Given the risks that I described above, you really shouldn't use data on the entities. Of course, there are workarounds. You can use UID. You can use custom configs.
But if you are working with JPA entities, it is actually easier to write the methods like equals hash code into string manually than to bend your architecture around a convenience library. The next pitfall debugging may become a nightmare. Lombok changes code at compile time. So the code that runs is actually not the code that you see in your ID. Therefore, you can spend a lot of time trying to find the root cause of some exception. You can't step into generated methods and the struct may be misleading. Of course, you can use Dumbok for debugging, but do you really want additional work for already not so exciting activity such as debugging? Okay, but is there actually anything good in Lumbok? Well, there are some interesting annotations, but also with some pit holes. For instance, there's the stinky throw annotation. This annotation lets you throw checked exceptions without declaring them. Lumblo generates the code that JVM is fine with. When it sees sneaky throw, it wraps the code in a try finally block. If you provided the exception classes in the annotation, it catches those exceptions. Otherwise, it catches throwable. The question is why does this code compile? Well, checked or unchecked is uh actually forced by the uh Java compiler. The JVM bite code doesn't care and so long links on that. But sometimes this behavior breaks and results in a bug. A use case for when the sneaky throws annotation can be very useful is an impossible exception. For example, the bite input stream extends the input stream that throws IO exception in the read method. So the read method of the bite input stream must also throw this exception. But the IO exception can never happen because no IO that uh happens there can throw this exception. But you still must catch this exception or the code won't compile. The sneaky throws annotation can help you reduce this annoying boiler plate. The downside well you are hiding the contract. Future you or your teammates will not see that a method can blow up. the frameworks that uh rely on declared exceptions will not see that either.
So use this annotation sparingly. Another interesting annotation is the extension method annotation. It is an experimental annotation and it lets you call static utility methods as if they were instance methods. So the method to title case which receives a string name becomes name to title case. This reads nicer and feels fluent, but it's just a compiled time sugar. No real methods get added to string. Also, if the receiver is null, there is no automatic null pointer exception. This null is passed straight into the static method. Again, use this annotation sparingly. The synchronized annotation is claimed to be a safer variant of the synchronized. Instead of locking on this on which other code can also lock on which may lead to deadlocks, long block locks on the private lock field. As a result, you get the same monitor semantics with fewer surprises, but it still serializes the whole method. So don't overuse it or you may throttle the throughput. The locked annotation is the younger sibling of the synchronized annotation. It wraps the method body in the re-entrant lock that supports features like try lock or interruptible locks. Plus, it's better for virtual threads than synchronized, but it's still locking because deadlocks are the design problem, not the syntax problem. And you can over serialize the hot paths if you overuse it. The cleanup annotation automatically adds a try finally block to the local variable and calls close or another method that you specify at the end of the scope. But don't use it where try with resources is better or when you use a fine grain control of the exceptions. The main goal and the main reason why Lombok is still used is that it was designed to reduce the notorious Chawa boiler plate. But if it is so risky, are there any alternatives? Good news is that yes, there are. Modern Java has records. Records are immutable data carriers. They only require from you the specification of the fields. Under the hood, they make all fields private and final and automatically create accessors, constructors, equals, hash code, and string. So no boiler plate and no long. Unfortunately, records are not suitable for JPA entities. But in this case, if you use intellig idea, for instance, you can simply hit generate and all the boilerplate methods that you hate will be generated automatically. All in all, you should carefully consider using Lombok in your project. And you must of course take the future of your project into consideration. What if after this video or after careful consideration you decided to get rid of Lumbok although your project uses it? The good news is that there is the Dylan book feature provided by the Lombok itself. It can help you clean your code of Lumbok annotations and substitute them with the real code. Dylan will duplicate the contents of the source directory into the source Dylan directory. It is good for debugging the lumbocked code or actually to get rid of that. If you found this video useful, don't forget to like it, subscribe to our channel, and until next time.





