Saturday, February 15, 2020

Bank on Lombok in a Spring Boot application . . .

It's been over a decade since my eyes had seen Java boiler plate code like getters, setters, various overloaded constructors,  toString(), equals(), hashCode() methods etc. My brain and eyes got used to very quiet and clean code. Now, I suddenly realize the fact that I have been quietly (joyfully) coding in Groovy for a long time. I am back to Java and all that noise is back and started to bother both my brain and eyes :(

To push all that noise away from your eye-sight into compiled Java byte-code, there is this nice Java library called: Lombok. Java developers never say NO to another jar file dependency as Java world simply loves to have tonnes and tonnes of libraries in projects, anyway ;)

Lombok is a neat Java library, both developer and compiler friendly, saves a lot of time, makes code look less noisy, and increases the life of both keyboard and your fingers ;). It provides various useful annotations to generate all that boiler plate code into compiled byte-code to please Java compiler and many Java frameworks. There are many resources and blog-posts on Lombok. I am only describing few annotations that I have explored in the context of Spring Boot with JPA and thought would be useful across many Java projects. I will definitely take Lombok with me into every Java project that I get into.

Environment: Java 13.01, Spring Boot 2.2.4.RELEASE, Maven 3.6.2, IntelliJ IDEA ULTIMATE 2019.3 on macOS High Sierra 10.13.6

All you need to start leveraging Lombok in any Java project is just a dependency in your build configuration(maven/grade). That takes care of giving you the power to auto-generate all that noise and push it away into byte-code by annotating your code, when your code gets compiled as part of the build process. But, IDEs compile code as we write and may need a bit more setup in order for the compiled classes to have all boiler plate code generated into the bytecode.

IntelliJ IDEA Support and Setup

IntelliJ IDEA requires the following 2 steps:
  1. Install Lombok plugin.
      Press Cmd + , (⌘,) or go to IntelliJ IDEA > Preferences
      Click Plugins, Search for Lombok and install
  2. Enable Java compiler feature: Annotation Processors.
      Press Cmd + , (⌘,) or go to IntelliJ IDEA > Preferences
      Go to Build, Execution, Deployment > Compiler > Annotation Processors and Check Enable Annotation Processing


Eclipse based IDE Setup

Check this article: Setting up Lombok with Eclipse and IntelliJ

Some Useful Lombok Annotations


This annotation takes a Java POJO (Plain Old Java Object) nearer to Groovy POGO (Plain Old Groovy Object) by taking away lot of boiler plate methods. Typically, domain objects do not contain any logic other than fields/properties to carry data for persistence. JPA Entities or any kind of objects that carry data are good candidates to leverage this annotation. Annotate a class with this and forget all getters, setters, toString(), hashCode(), equals() etc.


Annotating a class with this, you don't have to worry about providing a constructor to initialize required object properties to initialize the object with. Very useful in Spring beans/components like Services where in you typically write an all args constructor that takes all dependency beans and set the required dependencies. This is preferred over using @Autowired for dependencies for various good reasons. In this case, if you add a new dependency to an existing service, you don't have to worry changing/missing-to-change the constructor.

Also, in Enums if you have extra properties set for each enum instance, you can skip writing and maintaining a constructor which is required by annotating an enum with this.


Usually no args constructor, also called default constructor comes free and provided by Java compiler. By writing specific constructors, this freebie is taken away. On those instances, you still may need to provide this constructor for frameworks that need it. This annotation is useful in such cases.

@RequiredArgsConstructor

Useful in a SpringBoot application when you use constructor based injection than field based  injection (@Autowired). Constructor based injection is preferable than field based injection anyway for various good reasons. In this case, you typically declare all required dependent beans as static final fields by providing a constructor that initializes all of those required beans. SpringBoot auto injects all those beans by calling the constructor.

This annotation is right for this kind of situation with which you don't need to write the constructor and maintain it as you add more dependency beans. Also, with this the moment you add another static final required bean dependency, somewhere in your unit tests where you had used this provided constructor to initialize dependencies fails to compile right away.


These flexible annotations for fields/properties of a class reduce Java bean noisy methods required by many Java frameworks like Hibernate. This itself is good relief for eyes!

@Builder

This annotation brings in builder pattern implemented into the bytecode. Oftentimes, simple POJOs contain many properties. Creating and object becomes bit complex by traditional POJO way of create an object and populate properties by calling setters one by one which may lead to missing setting some properties. A builder pattern brings in fluent object creation by using a builder method followed by setters and the end calling a method to build.


If you use builder pattern/support provided to facilitate readable complex object instantiation and have an object hierarchy, you need to annotate your super class(es) with this annotation for all the properties inherited from the super class to be available to build setter methods. Though it is still listed as an experimental feature, it is very useful and safe to use.


It is typical in Java code you may write or come across utility classes with just static methods. Code coverage tools like JaCoCo report the class definition line (e.g. public class MyUtilityClass {) as uncovered for these classes as there won't be an instance created. You can fool the tool by just creating an instance of it, but that's stupid to do to get coverage. Even if you make the class final and provide a private constructor to fully protect it from creating an instance (a typical utility class should be like this anyway), this additional noise will not get any coverage as there won't be any test for private constructor to get coverage. Also, there is NO reason to break your head to get coverage for private constructor.

So, the best way is to take away all that noise from code into byte-code and exempt it from coverage. The annotation @UtilityClass gives you exactly this by making the class final and providing a private constructor in the byte-code. It not only takes away the noisy boilerplate code away but also improves the coverage as you tell JaCoCo anyways to ignore Lombok generated methods in byte-code. Neat!

Code coverage is only a measure to see how much of code is covered in automated tests. But little things like these add up and bring down the total percentage way down in some cases. It's a time saver if all such nasty noise goes away into byte-code without even bothering about code coverage.

e.g.
/** * This class is lean and clean. The annotation takes away boiler-plate code like final with private constructor into bytecode. * Also, all public methods are static. * Once you write tests for all methods and conditions, you are guaranteed to get 100% coverage. */ @UtilityClass public class MyUtil { public final String MY_CONSTANT = "Just a constant!"; public void m1() { ... } public void m2() { ... } }


Java's NullPointerException is a billion dollar mistake. Though Java is strongly typed language, the weakness lies in the null type and compiler doesn't provide any mechanism to safeguard that null reference. Kotlin addresses this issue by distinguishing types further into nullable types and non- nullable types and enforcing checks during compilation time. This is one of Kotlin's selling and compelling features to Java developers.

Checking each argument of each method for null is so much of noise in code. Java 7's added Objects.requireNonNull() method may only lessen the noise by eliminating the need for if(arg != null){...} else {...} kind of checks with one statement per argument, but still is smelly and noisy.

Java SE 8 added another convenient class java.util.Optional<T> around this problem to deal with in code, which helps design better APIs by indicating that the users whether to expect a null and forcing them to unwrap Optional object to check for the value. Also, it provides some convenient methods to make code more readable. However, it is not a solution to replace every null reference in your codebase.

Lombok's annotation @NonNul comes to rescue. Every method argument that cannot be null can simply be annotated with this which eliminates all the noise and makes code lot more readable. The intent goes into method definition. Under the covers, it just wraps the method body with a similar if null else check that we write otherwise. All that is invisible and is only visible in bytecode. Using this annotation doesn't take away the response to write tests for these null conditional checks if you have code coverage tools like JaCoCo used which still sees all such if conditional check in the bytecode anyway. It doesn't make sense to add more boiler plate code in unit tests by writing test cases just to test those if null check generated into byte-code. Fortunately, there is a Lombok setting that can tell JaCoCo to ignore these wrapped if null checks in byte-code.

lombok.nonNull.exceptionType=JDK

@Generated

Though there is no mention of this in the list of annotations in Lombok's stable or experimental features, it's good to know that there is one like this not for developer's to use in the code, but is for tools to indicate tell not to bother checking for coverage. The api doc has enough details on this.

@Slf4j

Last but not least, Lombok comes with variations of logging annotation for all widely used logging implementations in Java.

Logging is absolutely a needed feature in any application. In Java world, this feature becomes noisy as your number of classes start to grow more than one. Every class/object that needs to log must order a logger object from the factory. The factory needs to know the class for which the logger is needed. This class you give to the factory is typically the class itself that is making an order to the factory. In doing so, every class that needs to log must have a static final logger field initialized with the classname passing to the log factory.

All that factory business was very exciting in the beginning of this millennium. After two decades, there is no reason to have all this routine noise from the log factories to be visible in the code. In my opinion, this one annotation alone is good enough for adding Lombok to a Java application. Annotate classes with this and move that business with factories into bytecode.

Tips


When a class is annotated with Builder annotation, make sure that in some cases for frameworks like Jackson used for JSON serialization that require no-are constructor. The @Builder Design Pattern takes away the default no-arg constructor and forces one to use builder method to create an object. This will make frameworks that leverage no-arg constructor fail.

In this case, your better option would be to add @NoArgsConstuctor and @AllArgsConstructor in addition to @Builder. Both constructor annotation are needed.

@Data, @Getter, @Setter - override specific getter(s) or setter(s)

When a class is annotated with @Data, or @Getter and @Setter, getter and setter methods are generated. If due to any reason if a custom/overriding getter or custom setter is needed for any property, simple provide one the way you would like to, following Java bean style. Lombok won't generate for those ones you have provided.

JaCoCo - code coverage

If you have Java Code Coverage tools like JaCoCo configured for your project and a high coverage threshold level is set, you will get disappointed with the coverage metrics showing the levels suddenly dropping down due to Lombok. This is all due to JaCoCo working at the bytecode level considering all methods including constructors, getter, setters, hashCode etc. that got generated by Lombok. This boiler plate code that got synthesized by Lombok during compilation time doesn't need code coverage. In order to tell JaCoCo not to consider Lombok generated code in the bytecode, create a file with name lombok.config at the root of your project and have the following properties. Your coverage numbers will come back to normal.

# Jacoco >= 0.8.0 and lombok >= 1.16.14 feature # This property adds annotation lombok.@Generated to relevant classes, methods and fields. Jacoco code-coverage # identifies, detects and ignores all Lombok generated boilerplate code getter, setters, hashCode, builder etc. lombok.addLombokGeneratedAnnotation = true # Set lombok to throw Just JDK NullPointerException (default anyway) in the wrapped code. # Also, let JaCoCo honor and not complain coverage for if(!null){} method wrapper generated in the byte-code lombok.nonNull.exceptionType=JDK # Stop Lombok from serching for config files further config.stopBubbling = true

Summary

Lombok is a pretty neat Java library which not only takes noise away from code into bytecode, but also makes code more readable by showing the intention clearly with annotated code. The minimalist phrase "Less is more" becomes a reality with Lombok's addition to a Java project.

Source Code is for Java developers, whereas bytecode is for Java virtual machine. Noise is noise for humans, but not for machines. Java is evolving and changing fast, but still is very noisy and verbose. Any little effort made to make code less noisy and more readable goes a long way in the life of any Java project by saving lot of developers time who read the code later. After all code is written once, but read many times by many in the life of a project.

"Lean and clean" is always beautiful, makes everyone smile and feel better ;)

Make friendship with Lombok, stay healthy, keep your eye-sight better, and your brain calmer!!

References




1 comment: