Lombok Is Still Relevant in the Age of Java Records
swaranga.dev
swaranga.dev
Adding a pile of annotations throughout your codebase, often five stacked on top of one another, which generate behavior that is not locally visible, was never a sane trade-off for saving a little boilerplate.
Also the argument about Jackson and reflection magic is no longer true since JDK21. All Json frameworks now require explicit default constructors otherwise the throw RuntimeExceptions.
Coding agents are not omniscient gods. Cognitive complexity of code is still a thing for LLMs. And every line of code that the LLM writes is still a line that needs maintenance, and has bugs.
Software engineering rules still apply. Coding agents move the needle somewhat on "what can be maintained by us vs. what someone else can maintain" but not _that_ far. And the same "Don't maintain it if you don't have to" approach still applies. More code to maintain means bigger context required, which means the agent burns more tokens to do a task. More context also means the agent makes more mistakes and adds more bugs.
Abstractions are still a good idea. Handing off maintenance through abstractions is still a good idea. Good software design is still a good idea.
agents make dealing with boilerplate easier, but that doesn't change that boilerplate itself is just bad
Annotations like @RequiredArgsConstructor also encourage poor style, because there is no room for validation inside the constructor. At most, @NonNull adds null checks, and that is all. Lombok still offers many annotations that promote bad practice or have been superseded by the language itself, e.g. @Setter (mutability without protecting invariants), @Value (records) or @Cleanup (try-with-resources).
@Slf4j can easily be replaced by a single line of code (with the Log4j 2 API, even without specifying the class name).
Lombok's builders are actually useful, since Java has no named parameters with default values (as Kotlin does).
Regarding the "Dark Magic" section: just because other frameworks like Hibernate apply their own flavour of dark magic doesn't make it any better. Such magic should be kept out of the functional core of application code. The more magic is applied, the more problems can occur (e.g., the correct order in which tools apply their magic). This is essentially accidental complexity introduced to improve developer experience, but in the age of AI this may be the wrong optimization.
If you write modern Java, Lombok really doesn't add much.
It’s also funny because there is true irl “dark magic” for Java, Lombok and Bali.
Also surprised there isn’t much mention of Bali in general.
@requiredargs sucks, if you rearrange the order of two fields with the same type on a class you invisibly break the code. If you have multiple constructors (requiredargs, allargs) it makes tracing how they’re used using the built in tools of your IDE impossible
@builder fucking sucks, if there’s non-nullable fields and you don’t set them, at best you get a runtime exception. Builder pattern should take non-null args explicitly in the builder() method so when you add a new required field it doesn’t invisibly break code 8 modules away
Only good thing is indeed the log annotation, and to some degree @getter @setter, big whoop
It is possible but requires more key strokes. It is annoying, but possible.
To be clear, I know how to look up where the class is used, and i know how to look up where constructors (note, plural) for the class is being used, but I have not found a reliable way to find where a specific constructor is being used if the class has multiple
Ctrl-O for a popup window of all methods. The lombok generated constructor is there. Highlight it and then shortcut for call hierarchy. Ctrl-h or ctrl-alt-h I am not sure now.
And I say this as someone who has been writing Python for a living for the past 4 years who rather misses Java and the ecosystem.
e.g. poor support, or late support for
- JWT/Swing desktop UIs
- Web technologies (it used to have a built in JS engine, competing standards, servlets, jsps, jstl, portlets)
- Image processing (ImageIO is limited, Image and BufferedImage were slow)
- Native interop (overlapping / competing standards)
- RPC (CORBA, Jini...)
- Mobile variants (J2ME, Java Profile Variants, Embedded Java, Java card)
- Threads, Concurrency, native threads (confusing so many re-implementations)
- Delegates, sorry I mean arrow functions, oops no I mean lambdas. The features Sun said should not be in the language for years then back pedalled once it realised the foot gun calling out Microsoft for them.
So many attempts at relevance then abandoned, left in the language as cruft, or just ideas that were vehemently not Java, shoe horned in.
And they still don't have the Elvis operator, not for lack of trying though, they do have the abomination Optional instead though, but best practice says DON'T use that for arguments, just return types (but the language permits it anyway).
For newbies, Python is better, Typescript is more accessible for web or serverless, Rust for native...