Technology•Sep 09, 2014
Project Lombok or How I Learned to Stop Worrying and Love Autogeneration Through Annotations
Project Lombok is a Java library and integrated development environment (IDE) plugin that eliminates the need for common boilerplate code. A few simple annotations can grant Java classes the elegance of C#’s properties and var features, negate the need for code commonly auto-generated by IDEs, and provide automatic implementations of common design patterns. Lombok achieves this by generating application code when compiled based on annotations without modifying the original source files, while making IDEs aware of what will eventually be generated. This post covers several useful Lombok features and tips for avoiding common pitfalls.
By putting @Getter and/or @Setter above a class’s member variable, Java bean style getter and setter methods will automatically be generated for it. Putting these annotations on the class itself will generate getters and setters for all of its member variables. Manually implementing a getter or setter within the class will automatically override the behavior of the Lombok annotation, which can be useful when they have been placed at the class level.
When used in moderation on small data oriented classes, this annotation can save time and space. But be careful if the class already has a lot of annotations and other methods—these annotations may get lost in the crowd, and it may not be obvious that these getters or setters exist.
Use of these annotations also prevents global rename refactoring operations on the member variable’s getters and setters. This can still be achieved by temporarily adding a getter and setter manually, refactoring their method names while changing the name of the member variable to match, and removing the manually added getter and setter. Also, you won’t be able to perform a call hierarchy on the getters/setter methods unless you go to an outline (Eclipse) or structure (IntelliJ) view.
Additionally, putting @Getter on a Boolean object will prefix the generated getter with “get”, while putting it on a primitive Boolean will prefix the getter with “is”. If you’re applying this annotation to an existing codebase that hasn’t been named following this scheme, performing a global rename refactoring operation first will prevent compilation errors that would be encountered when migrating to @Getter.
@Log and its variants are a safe and easy way to add loggers without copying and pasting the same logging declaration line between every class. Using this approach can cut down on copy/paste errors, such as leaving the wrong class name in a copied logger declaration line.
// No more need for this: private final static Logger logger = Logger.getLogger(ClassName.class);
Instead of using the following line of code:
VeryLongObjectTypeName veryLongObjectTypeName = new VeryLongObjectTypeName();
The val Object lets you write the following line instead:
val veryLongObjectTypeName = new VeryLongObjectTypeName();
The only caveat to the use of val is that the variable will always be final, making it equivalent to:
final VeryLongObjectTypeName veryLongObjectTypeName = new VeryLongObjectTypeName();
The val keyword can be handy when it doesn’t really matter to the reader what type the object is or if the type is very obvious due to its name. It’s usually safe to use within method declaration lines since the type of the object is already stated at the end of the line. However, more careful judgment should be used when determining whether or not val is appropriate for storing values returned from executed methods, since the concrete type is not readily available elsewhere on the same line. Val can save time when writing methods, but it shouldn’t be used if it hampers readability.
Val can be especially useful when two imported packages have conflicting class names since it can prevent long package names from cluttering up method implementations. For example:
// class from another library with a conflicting name val externalTime = new org.external.api.Time();
// class from this project with the same name val internalTime = new Time();
May be preferable to reading:
org.external.api.Time externalTime = new org.external.api.Time(); Time internalTime = new Time();
Val can also save time when implementing methods for the first time or when dealing with code that undergoes frequent changes, since it reduces the number of class names that need to be typed during development. The keyword can easily be removed once the method has been finalized if doing so would improve readability.
While val is arguably Lombok’s most valuable feature, it is not fully supported in Netbeans and IntelliJ. While code that uses val will still compile and run correctly in those IDEs, all uses of val will be flagged as being syntactically incorrect. If your project’s team is not using Eclipse, be sure to avoid the val keyword to prevent false positive errors from being highlighted.
@AllArgsConstructor and @NoArgsConstructor
Placing @AllArgsConstructor on a class automatically creates a constructor that requires each of the class’s fields. Because the constructor’s method signature will automatically change whenever a new field is added to the class, it will not be possible for the developer to forget to update an old constructor that was supposed to fully initialize an object of that class. This annotation is a great way to cut down on potential bugs caused by old constructors that may have been auto-generated and not properly updated as the class evolves.
Note that if the fields are re-ordered within the object, they will be re-ordered in the constructor’s method signature too. If multiple fields have the same type they might not cause a compilation error when their order is switched, which could increase the chance of a bug being introduced by not correcting all of the callers.
This annotation does have one major limitation—it is not aware of superclasses. @AllArgsConstructor will only add member variables of the annotated class to that class’s constructor. A manual Java constructor will have to be written for any other constructor that requires one or more arguments.
Placing @NoArgsConstructor on a class will create a no-argument constructor. While it’s easy to do the same thing without Lombok, using this annotation looks a little cleaner and leaves one less element to rename if the class’s name changes. If you’re already using @AllArgsConstructor on a class that will be persisted by Hibernate, it’s worth the extra annotation to provide a no-argument constructor (as per the Hibernate documentation).
Placing @Delegate on a member variable will create delegate methods for all of the methods within the class that contains it. This looks like an elegant way to implement the delegate pattern, making it easy to work around multiple inheritance design issues by favoring composition over inheritance. However, @Delegate has been deprecated to experimental status in the latest Lombok release and may be removed in future versions, so using it is not recommended.
Using @Delegate might make it confusing to find the delegated methods when looking at the containing class, especially if large amounts of other annotations are being used. Additionally, if multiple @Delegate annotations are used within the same class, there is no easy way to tell which methods are delegates to which annotated class.
@Delegate also has an unintuitive way of overriding its behavior. In order to avoid creating delegates for certain methods, an interface must be passed to the annotation as an argument in order to specify the method names to ignore.
Given the confusion @Delegate can cause and its deprecated status, this particular annotation looks promising but fails to deliver a reliable solution.
Instead of using your IDE to re-generate your equals and hash code methods every time you add a new field to your object, @EqualsAndHashCode will do the same thing and always be up to date.
@ToString will automatically generate a toString method that prints the class’s name and all of its fields.
@Data is an aggregate annotation that is equivalent to using:
@RequiredArgsConstructor (which works like @AllArgsConstructor for final member variables)
@Getter and @Setter
This makes it very simple to make a class easily store data without dozens of lines of boilerplate code or a large amount of Lombok annotations.
Is There More?
Many more annotations can be found at Project Lombok’s features page, such as lazy getters, automatic implementations of the builder pattern, and easy implementations of synchronized blocks. Be sure to check out their examples for comparisons of code with and without Lombok, and additional arguments for each of the annotations discussed in this blog to tweak their behavior.
How Do I Start?
To use Project Lombok, simply download the JAR from Project Lombok’s website and include it in your project’s classpath. If you use Maven, Ivy, or Gradle, you can import it using one of these configurations. You will also need to enhance your IDE’s code completion and validation via additional configuration and/or plugins, as detailed at Project Lombok’s downloads page.
Lombok has great Eclipse support. The latest Eclipse releases have always been supported right out of the gate. However, some features (such as val) do not currently work on other IDEs, and IntelliJ’s Lombok Plugin is not always guaranteed to immediately support the latest IntelliJ releases. If your project is open source or has developers whose IDEs might not be compatible with the features you want to use, it might not be possible to use Lombok or those specific features within your project.
The IntelliJ Lombok plugin officially supports versions 10.5.4, 11.1.5, 12.1.7, and 13.1.1. Personal testing has also shown that it works on the latest IntelliJ version, 13.1.4. If you’re having trouble using the IntelliJ Lombok plugin, try searching for it under Compiler -> Annotation Processors -> Lombok, and check its “Enable annotation processing” setting (as discussed on this Lombok issues page).
What if I Don’t Like It?
If you decide you want to remove Lombok from your project, the delombok process can be run to generate alternate versions of your Java files that replace all Lombok annotations with their equivalent generated source code. This is especially useful for Javadoc generation. You can also manually remove all of the Lombok annotations from your project and in most cases, your IDE will provide ways to auto-generate the boilerplate code that Lombok replaced.
If you’re working on an open source project or have a large number of contributors, it might not be worth using Lombok due to potential waiting periods for IDE plugins to support the latest IDE build. Additionally, the val keyword should be avoided in such projects since it’s only supported in Eclipse. However, if you’re working on a project where you can ensure that developers use compatible IDEs, Lombok can be a great pleasure to work with. It makes day-to-day Java programming less painful and can make source code much more readable for the low cost of an IDE plugin and knowledge of how to safely use its features. You can also brag to your friends about how great it is to have features similar to C#’s properties and var in your Java codebase—it’s a great conversation starter!
If you have any additional questions or comments, please leave a message in the comments section below or contact us at firstname.lastname@example.org. You can also follow us on Twitter at @CrederaOpen or connect with us on LinkedIn.