Lombok makes Java cool again
Omar Ismail
Senior Software Engineer @ Digitinary | Java 8 Certified? | Spring & Spring Boot?????? | AWS? | Microservices ?? | RESTFul Apis & Integrations ?? FinTech ?? | Open Banking ?? | Digital Payments and Transformation??
Setting up Lombok
Lombok is a compiler plugin, as it converts the annotations in your source code into Java statements before the compiler processes them — the Lombok dependency does not need to present at runtime, so adding Lombok will not increase the size of build artifacts. So you’ll need to download Lombok and add it to your build tool. To?set up Lombok ?with Gradle (it works with?Maven ?too), add this block to the build.Gradle file:
Since Lombok is a compiler plugin, the source code we write for it is actually?not?valid Java. So you’ll also need to install a plugin for the IDE you are using. Fortunately, Lombok supports all the major Java IDEs. Without the plugin, the IDE has no idea how to parse the code. The IDE integration is seamless. Features such as “show usages” and “go-to implementation” continue to work as expected, taking you to the relevant field/class.
Lombok in action
The best way to learn about Lombok is to see it in action. Let’s dig into some examples on how to apply Lombok to common aspects of a Java application.
Spice Up A POJO
We use plain old Java objects (POJOs) to separate data from processing, making our code easier to read and simplifying network payloads. A simple POJO has some private fields and corresponding getters and setters. They get the job done but only after writing a lot of boilerplate code.
Lombok helps make POJOs more useful, flexible, and structured without writing much additional code. With Lombok, we can simplify the most basic POJO with the?@Data?annotation:
The?@Data?annotation is really just a convenience annotation that applies multiple Lombok annotations.
That one annotation covers many common use cases simply and elegantly. But a POJO isn’t always enough. A?@Data?class is entirely mutable, which when abused, can increase complexity in an application and limit concurrency usage, both of which hurt the longevity of an application.
Lombok has just the fix. Let’s revisit our?User?class, make it immutable, and add a few other useful Lombok annotations.
All it takes is the?@Value?annotation.?@Value?is similar to?@Data?except, all fields are made private and final by default, and setters are not generated. These qualities make?@Value?objects effectively immutable. As the fields are all final, there is no argument constructor. Instead, Lombok uses?@AllArgsConstructor?to generate an all-arguments constructor. This results in a fully-functioning, effectively-immutable object.
But being immutable isn’t very useful if you can only create an object using an all args constructor.?As?Effective Java?by Joshua Bloch?explains, builders should be used when faced with many constructor parameters. That is where Lombok’s?@Builder?steps in, automatically generating a builder inner class:
Using the Lombok-generated builder makes it easy to create objects with many arguments and to add new fields in the future. The static builder method returns a builder instance to set all the properties of the object. Once set, invoke?build()?on the builder to return an instance.
The?@NonNull?annotation can be used to assert that those fields are not null when the object is instantiated, throwing a?NullPointerException?when null. Notice how the avatar field is annotated with?@NonNull?but it is not set. That is because [email protected]?annotation indicates to use “default.png” by default.
Also notice how the builder is using?favoriteFood, the singular name of the property on our object. When the?@Singular?annotation is placed on a collection property, Lombok creates special builder methods to individually add items to that collection, rather than adding the entire collection at once. This is particularly nice for tests as creating small collections in Java is not concise.
Finally, the?toBuilder = true?setting adds an instance method?toBuilder()?that creates a builder object populated with all the values of that instance. This enables an easy way to create a new instance prepopulated with all the values from the original instance and change just the fields needed. This is particularly useful for?@Value?classes because the fields are immutable.
A few annotations let you further configure specialized setter functions.?@Wither?creates “withX” methods for each property that accept a value and returns a cloned of the instance with the one field value updated.?@Accessors?lets you configure automatically created setters. By default, it allows setters to be chained, where like a builder, this is returned rather than void. It also has a parameter,?fluent=true, which drops the “get” and “set” prefix convention on getters and setters. This can be a useful replacement for?@Builder?if the use case requires more customization.
If the Lombok implementation does not fit your use case (and you have looked at the annotation’s modifiers), then you can always just write your own implementation by hand. For example, if you had a?@Data?class but a single getter needed custom logic, simply implement that getter. Lombok will see that implementation is already supplied and will?not?overwrite it with the autogenerated implementation.
With just a few simple annotations, the initial User POJO has gained so many rich features that make it easier to use without putting much burden on us engineers or increasing the time or cost to develop.
Remove component boilerplate code
Lombok isn’t just useful in POJOs — it can be applied at any layer of an application. The following usages of Lombok are particularly useful in the component classes of an app, such as controllers, services, and DAOs (data access objects).
Logging is a baseline requirement for every piece of software, serving as a critical investigation tool. Any class that is doing meaningful work should be logging information. As logging is a cross-cutting concern, declaring a private static final logger in every class becomes instant boilerplate. Lombok simplifies this boilerplate into one annotation that automatically defines and instantiates a logger with the right class name. There are a handful of different annotations depending on the logging framework you are using.
With a logger declared, next, let’s add our dependencies:
The?@FieldDefaults?annotation adds the final and private modifiers to all of the fields. The?@RequiredArgsConstructor?creates a constructor that accepts and sets a?UserDao?instance. The?@NonNull?annotation adds a check in the constructor and throws a?NullPointerException?if the?UserDao?instance is null.
But wait, there’s more!
There are so many ways to use Lombok. The above two sections focused on specific use cases, but Lombok can make development easier in many areas. Here are a couple of small examples that show off how to better leverage Lombok effectively.
Although Java 9 introduces the?var?keyword, a?var?can still be reassigned. Lombok provides a?val?keyword which picks up where?var?leaves off, providing local final type inferred variables.
Some classes just have pure static functions and are never meant to be initialized. Declaring a private constructor that throws an exception is one way prevent it from getting instantiated. Lombok has codified that pattern in its?@UtilityClass?annotation which creates a private constructor that throws an exception, makes the class final, and makes all methods static.
A common critique of Java is the verbosity created by throwing checked exceptions. Lombok has an annotation to remove the need for those pesky throws keywords:?@SneakyThrows. As you might expect, the implementation is quite sneaky. It does not swallow or even wrap exceptions into a?RuntimeException. Instead, it relies on the fact that at runtime, the JVM does not check for the consistency of checked exceptions. Only javac does this. So Lombok uses bytecode transformations to opt out of this check at compile time. As a result, this results in runable code.
Side by side comparison
Nothing beats seeing how much code Lombok saves than doing a side by side comparison. The IDE plugin offers a “de-lombok” function that converts most Lombok annotations into the approximate native Java code (the?@NonNull?annotation not converted). Any IDE with the Lombok plugin installed lets you convert most annotations into native Java code (and back again). Let’s return to our?User?class from above.
The Lombok class is just 13 simple, readable, descriptive lines of code. But after running de-lombok, the class is transformed into over a hundred lines boilerplate that no one wants to see, but everyone wants!
We can do the same for the?UserService?class from above.
Will result in approximately this Java code.
Measuring the impact
Grubhub has over a hundred services running to accomplish the needs of the business. We took one of these services and ran the “de-lombok” functionality of the Lombok IntelliJ plugin to see how many lines of code were saved by using Lombok. The result was a change to approximately 180 files, resulting in about 18,000 additional lines of code and 800 deletions of Lombok usages. That is 18,000 lines of auto-generated, standardized, and battle-tested lines of code! On average, each line of Lombok code is saving 23 lines of Java code. With an impact like that, it is hard to imagine using Java without Lombok.
Summary
Lombok has been an excellent way to excite engineers with the appearance of new language features without requiring much effort across the organization. It is certainly easier to apply a plugin to a project than to train all engineers on a new language and port over existing code. Lombok may not have everything, but it certainly provides enough out of the box to have a noticeable impact on the engineering experience.
One other benefit of Lombok is that it keeps our codebases consistent. With over a hundred different services and distributed teams across the world, keeping our codebases in alignment makes it easier to scale teams and reduce the burden of context switching when starting a new project. Lombok is relevant for any version of Java since 6, so we can count on it being available in all projects.
Lombok means much more to Grubhub than just shiny new features. After all, anything Lombok does?could?be just written by hand. As shown, Lombok streamlines the boring parts of the codebase without affecting business logic. This keeps the focus on efforts that provide the most value to Grubhub and are the most interesting to our engineers. It is a waste of time for the writer, reviewer, and maintainer to have such a large portion of the codebase be monotonous boilerplate code. Also, as this code is no longer manually written, it eliminates entire classes of typo errors. The benefits of autogeneration combined with the power of?@NonNull?reduce the chance of bugs and keep our engineering efforts focused on delivering food to you!
Credit: Grubhub Bytes