Lombok is one of the tools we are using in almost all of our projects. It is a neat little library helping to reduce the boilerplate code and and make our developers life a little bit easier.
1) What is Lombok and how it works
Lombok provokes a lot of different opinions. Part of the developers love it, because it allows more succinct code and reduces the boilerplate so typical for the Java programming language. Another part of the developers, really don’t like the “hacky” nature of the library. It hooks in “Annotation Processing” phase of Java compilation process and performs bytecode manipulations, based on metadata provided by annotations . You can read more about how Lombok makes its magic here.
I personally find using it really practical. It helps reducing the amount of source code without real business value saving time and lets me concentrate on more important tasks. It’s true that modern development tools provide a lot of code generation features and things like getters and setters can be generated in seconds, but still this is additional source code and it has to me maintained and this requires additional effort. With Lombok no new action is required when new property is added.
Another good thing about Lombok is that, runtime dependency isn’t required. It is only triggered compile time and after it makes its magic, you are ready to roll. This means that you can release your source code without having explicit dependency to the library. Additionally, if you at some point decide that Lombok is not your thing, you can use delombok tool provided by the creators of the library. This tool replaces all Lombok annotations with generated Java source code, removing source code dependency entirely.
Something you should have in mind when considering Lombok is that future Java changes can prevent it from working. Because the way it works it is sensitive to changes in some of the core Java APIs. For example Lombok team just recently announced Java 9 support. They had some difficulties because of changes related to modularization and JigSaw project.
With the time the support for Lombok increases and gets better and better. The integration with most of the widely used development tools is straightforward. You can get the latest version from Maven Central repository, add it as “compileOnly” dependency in Gradle 2.12 and that’s all. It has also integration with popular java IDEs. In IntelliJ, you have to install Lombok Plugin and enable “Annotation Processing” and you are ready to use it. Lombok plugin supports all framework features and additionally provided refactoring features like renaming, lombok and delombok. You can see more information on how to use it with other build tools and IDEs can be seen in the Install section.
3.1) Getters and Setters
Today many popular Java frameworks and enterprise patterns are based on usage of Java Bean / POJO style classes. Following the encapsulation principle, properties of these classes are declared private and can be manipulated through getters and setters. In many cases this leads to writing classes like this:
Here, big part of the source code has no real business value. In most cases it will be generated with modern IDEs, but still have to be maintained after that. With Lombok @Getters and @Setters annotations the source code can be significantly reduced. This class is fully equivalent to the class listed above. They can be used on class level or for each individual property. By default Lombok will generate getters and setters starting with “get”, “set” and “is” prefixes and public access. Access modifier can be customized using AccessLevel annotation property.
Lombok provides three annotations related to constructor generation.@NoArgsConstructor generates constructor with no arguments. @RequiredArgsConstructor generates constructor with one argument for each final non-static property. And @AllArgsConstructor generates constructor with one argument for each non-static property. One usage that I personally like is the combination with Spring beans. Using constructor dependency injection is considered a good practice, as is declaring all class properties final when possible. Following these principles often Spring beans look like that:
Using Lombok this can be replaced with:
Lombok will generate constructor for both final class properties and will add @Autowred annotation. Explanation of the wierd @__ syntax can be found on this Lombok page. As of Spring 4.3 in single constructor beans @Autowired annotation can be skipped and if you are using Spring 4.3 and later this can be further simplified to:
3.3) EqualsAndHashCode and ТoString.
Correct implementation of hashCode() can be tricky. There are a lot of resources on this topic with one of my favourite to be the implementation described by Josh Bloch’s Effective Java in item 8. As a rule of a thumb I always prefer to use IDE’s code generation, java.util.Objects utility or third party libraries for the implementation. Another option, which I started using with Lombok is @EqualsAndHashCode annotation. It will generate equals() and hashCode() methods for you in all annotated classes. By default it uses all non transient and non static fields and generates equals() and hashCode() based on them. If you want to customize the behaviour you can use exclude annotation property and list the fields you don’t want to be included. Additionally if your class is a part of object hierarchy you can include call to the parent class methods using callSuper.
@ToString annotation generates toString() method for you. It behaves similar to @EqualsAndHashCode and by default it uses all and non static fields to generate string representation. The behaviour can be customized with exclude, callSuper and of annotation parameters.
3.4) Beans, POJOs and Value objects
@Data and @Value annotations provide mechanism for reducing the boilerplate code typical for POJOs and Value object implementations. @Data can be seen as a collection of @ToString, @EqualsAndHashCode, @Getter / @Setter and @RequiredArgsConstructor annotations:
It generates toString(), equals() and hashCode methods. Constructor with parameters for all final and non static fields if no explicit constructor exists. Public getters and setters for all non final fields and non static fields. One downside is that @Data doesn’t provide the customization abilities of all separate annotations. Fortunately Lombok allows us to override the default behaviour using the explicit annotations.
Also if any of the methods is explicitly defined Lombok is smart enough and will skip it without any warning or error message.
@Value can be seen as immutable version of @Data annotation and helps creating and enforcing immutable value objects. In addition to generated toString(), equals() and hashCode() the class is made final. All fields are also made private and final and only getters are generated. Constructor is generated with parameters for all fields that are not initialized during the declaration. Again as @Data annotation, if any constructor is declared explicitly, Lombok will skip constructor generation.
Builders are handy way of initialization of complex objects. Again builder implementation requires a lot of trivial boilerplate code to be written. @Builder annotation can save us a lot of time and hustle. Annotating class or constructor creates public static class with name the class name plus Builder suffix. This class has build() method and contains appropriate initialization methods for all annotated class fields. Combined with @Singular annotation it will provide single argument method for all Iterable, Collections, Map fields.
Adding logger definition in each class can be really annoying. Because of that it is common practice just to copy and paste logger definition from another class. This often leads to errors, because the developers just forgot to change the target class.
Lombok provides logging annotations that can eliminate this problem and also reduce the amount of boilerplate code related to logger definition.
@Slf4j annotation creates Slf4j logger with name log and inserts it into the annotated class as private static final field. If you prefer another logging implementation Lombok provides annotations for those of them that are most widely used. You can find detailed description here.
3.6) Checked exceptions
In many cases checked exceptions only pollute method definitions and caller source code without contributing with any real value. It is common practice checked exceptions to be wrapped in runtime exception and simply rethrown eliminating the need all callers to handle them. Lombok provides @SneakyThrows annotation, which allows throwing checked exceptions without declaring them in method definition. This annotation is really handy in some situations, but it has to be used with care. If you want to catch sneaky thrown exception later in the call stack you won’t be able, because the compiler won’t let you catch not thrown exception in try block. You have to be careful if this is meaningful API exception or just “impossible” exception polluting the method declaration.
These are only part of the most common Lombok features we use. It provides additional customizations and more functionalities, which you can find on Lombok official website. It is really useful little library, helping to save time and reduce the boilerplate code. We will continue to use it in our Java projects. May be not for long, because Kotlin really looks great, but this a topic for another story.