Java is a great but verbose language. You may be ending up writing many lines of code even to achieve the most common goals. Plus, it definitely involves repetitive code, like getters and setters. This leads to a huge amount of boilerplate and avoidable code. Not only does this add nothing to the business logic of your application, but writing it is an unnecessarily boring and time-consuming process. This is why you should start employing tools and libraries to make you more productive by avoiding this. That’s where Lombok comes into play!
This Java library provides you with several annotations aimed at avoiding writing Java code known to be repetitive and/or boilerplate. Project Lombok works by plugging into your build process. Then, it will auto-generate the Java bytecode into your .class files required to implement the desired behavior, based on the annotations you used. Thus, each annotation offered by Project Lombok allows you to skip writing methods and logic you would like to avoid, like constructors, equals, and hash code functions. This will save you a lot of time and let you focus on the business logic of your project. Plus, you will be able to keep your codebase smaller, cleaner, and easier to be read and maintained.
First, we will see what Project Lombok is and how it works. Then, we will study the most common and relevant Lombok’s annotations, understanding what the most important ones are, where, and how to use them. Next, it will be time to see how to integrate it in your IDE (Integrated Development Environment) and why you should not be afraid of using it.
Prerequisites
This is the list of all the prerequisites to replicate the examples that will be shown next:
- Java >= 1.8
- Gradle >= 4.x or Maven 3.6.x
- Project Lombok >= 1.18.20
What is Lombok
Project Lombok (from now on, Lombok) is an annotation-based Java library that allows you to reduce boilerplate code. Lombok offers various annotations aimed at replacing Java code that is well known for being boilerplate, repetitive, or tedious to write. For example, by using Lombok, you can avoid writing constructors with no arguments, toString()
, equals()
, and hashCode()
methods by simply adding a few annotations. The magic happens during the compile-time when the library injects the bytecode representing the desired and boilerplate code into your .class files. Plus, as we will see, the library can be plugged into your IDE, letting you have the same experience as if you had written all the boilerplate code yourself.
You can easily install Lombok by adding lombok
to your dependencies.
If you are a Gradle user, add these two lines to the dependencies section of your build.gradle
file:
compileOnly 'org.projectlombok:lombok:1.18.20'
annotationProcessor 'org.projectlombok:lombok:1.18.20'
While if you are a Maven user, add the following dependency to your pom.xml
file:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
Plus, add the Lombok dependency to the maven-compiler-plugin
configuration section as follows :
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>11</source> <!-- depending on your project -->
<target>11</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- ... -->
</plugins>
</build>
Now, you have all you need to start using Lombok.
Most Common Lombok Annotations
Here you can find the most common and important Lombok annotations. Each of them will be explained and then seen in use compared to the equivalent Java vanilla translation. To see examples and get more support, click on each annotation and visit its page on the Lombok official documentation.
@Getter
, @Setter
When a field is annotated with @Getter
and/or @Setter
, Lombok will automatically generate the default getter and/or setter, respectively. The default implementation for getters simply takes care of returning the annotated field. Similarly, the default implementation for setters takes one parameter of the same type as the annotated field and simply sets it with the received value. When a field called value
is annotated with both @Getter
and @Setter
, Lombok will define a getValue()
(or isValue()
if the field is boolean
), and a setValue()
method. The generated getter/setter method will be public
, unless a particular AccessLevel
is specified. The allowed AccessLevel
values are PUBLIC
, PROTECTED
, PACKAGE
, and PRIVATE
. Please, note that you can also annotate the entire class. In this case, this logic will be applied to each field.
With Lombok
@Getter
@Setter
public class Author {
private int id;
private String name;
@Setter(AccessLevel.PROTECTED)
private String surname;
}
Java Vanilla
public class User {
private int id;
private String name;
private String surname;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
protected void setSurname(String surname) {
this.surname = surname;
}
}
@NoArgsConstructor
, @RequiredArgsConstructor
, @AllArgsConstructor
When a class is annotated with @NoArgsConstructor
, Lombok will take care of automatically generating a constructor with no parameters. Likewise, when annotated with @AllArgsConstructor
, a constructor with one parameter for each field in your class will be generated. Similarly, @RequiredArgsConstructor
leads to a constructor with a parameter for each field requiring special handling. In particular, this involves non-initialized final
fields, as well as any fields marked as @NonNull
that are not initialized where declared. Please, do not forget that static fields will be ignored by these annotations.
With Lombok
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
public class Author {
private int id;
private String name;
private String surname;
private final String birthPlace;
}
Java Vanilla
public class Author {
private int id;
private String name;
private String surname;
private final String birthPlace;
// @NoArgsConstructor
public Author() {}
// @AllArgsConstructor
public Author(int id, String name, String surname, String birthPlace) {
this.id = id
this.name = name
this.surname = surname
this.birthPlace = birthPlace
}
// @RequiredArgsConstructor
public Author(String birthPlace) {
this.birthPlace = birthPlace
}
}
@ToString
When a class is annotated with @ToString
, Lombok will take care of generating a proper implementation of the toString()
method. By default, a String containing the class name, followed by each field's value separated by a comma, will be returned. By setting the includeFieldNames
parameter to true, the name of each field will be placed before its value. By default, all non-static fields will be considered when generating the toString()
method. Annotate a field with @ToString.Exclude
to make Lombok ignore it. Alternatively, you can specify which fields you wish to be taken into account by using @ToString(onlyExplicitlyIncluded = true)
. Then, mark each field you want to include with @ToString.Include
.
With Lombok
@ToString(includeFieldNames=true)
public class Author {
private int id;
private String name;
private String surname;
}
Java Vanilla
public class Author {
private int id;
private String name;
private String surname;
@Override
public String toString() {
return "Author(id=" + this.id + ", name=" + this.name + ", surnname=" + this.surname + ")";
}
}
@EqualsAndHashCode
Annotate a class with @EqualsAndHashCode
, and Lombok will automatically implement the equals()
and hashCode()
methods for you. By default, all non-static, non-transient fields will be taken into account. You can modify which fields are used by annotating them with @EqualsAndHashCode.Include
or @EqualsAndHashCode.Exclude
. Alternatively, you can annotate your class with @EqualsAndHashCode(onlyExplicitlyIncluded = true)
and then specify exactly which fields or methods you want to be used by marking them with @EqualsAndHashCode.Include
. Please, note that the equals()
) and hashCode()
) methods will be generated by Lombok without breaking the contract between them. Follow the link on the two methods to the official Java documentation to learn more about the contracts that equals()
and hashCode()
implementations should fulfill.
With Lombok
@Getter
@Setter
@EqualsAndHashCode
public class Author {
private int id;
private String name;
private String surname;
}
Java Vanilla
public class Author {
// gettes and setters ...
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((surname == null) ? 0 : surname.hashCode());
return result;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Author)) return false;
Author other = (Author) o;
if (!other.canEqual((Object)this)) return false;
if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false;
return true;
}
}
@NonNull
You can annotate with @NonNull
a record component, a parameter of a method, or an entire constructor. This way, Lombok will generate null-check statements for you accordingly.
With Lombok
public class Author {
private int id;
private String name;
private String surname;
public Author(
@NonNull int id,
@NonNull String name,
String surname
) {
this.id = id;
this.name = name;
this.surname = surname;
}
}
Java Vanilla
public class Author {
private int id;
private String name;
private String surname;
public Author(
int id,
String name,
String surname
) {
if (id == null) {
throw new NullPointerException("id is marked @NonNull but is null");
}
this.id = id;
if (name == null) {
throw new NullPointerException("name is marked @NonNull but is null");
}
this.name = name;
this.surname = surname;
}
}
@Data
@Data
is a shortcut annotation that combines the features of @ToString
, @EqualsAndHashCode
, @Getter
@Setter
, and @RequiredArgsConstructor
together. So, @Data
generates all the boilerplate involved in POJOs (Plain Old Java Objects). This means, in particular, getters for all fields, setters for all non-final fields, proper toString
, equals
, and hashCode
implementations involving every field of the class, and a constructor for all final fields.
With Lombok
@Data
public class Author {
private final int id;
private String name;
private String surname;
}
Java Vanilla
public class Author {
private final int id;
private String name;
private String surname;
public Author(int id) {
this.id = id;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = prime * result + getId();
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
result = prime * result + ((getSurname() == null) ? 0 : getSurname().hashCode());
return result;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Author)) return false;
Author other = (Author) o;
if (!other.canEqual((Object)this)) return false;
if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false;
return true;
}
}
@Value
@Value
is the immutable variant of @Data
. This means that all fields are made private
and final
by Lombok by default. Plus, setters will not be generated, and the class itself will be marked as final
. This way, the class will not be inheritable. Just like what happens with @Data
, toString()
, equals()
and hashCode()
implementations are also created.
With Lombok
@Data
public class Author {
int id;
String name;
String surname;
}
Java Vanilla
public final class Author {
private final int id;
private final String name;
private final String surname;
public Author(int id, String name, String surname) {
this.id = id
this.name = name
this.surname = surname
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = prime * result + getId();
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
result = prime * result + ((getSurname() == null) ? 0 : getSurname().hashCode());
return result;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Author)) return false;
Author other = (Author) o;
if (!other.canEqual((Object)this)) return false;
if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false;
return true;
}
}
Advanced Lombok Annotations
Here you can find the most complex Lombok annotations. Each of them will be explained and then seen in use compared to the equivalent Java vanilla translation. To see examples and get more support, click on each annotation and visit its page on the Lombok official documentation.
@Cleanup
@Cleanup
can be used to ensure a given resource is automatically cleaned up before leaving the current scope. By default, the cleanup method of the annotated resource is assumed to be close()
, but you can specify the name of the desired method to be called instead. Note that this annotation works by harnessing the try-with-resources statement.
With Lombok
public class CleanupDemo {
public static void main(String[] args) throws IOException {
@Cleanup
InputStream input = new FileInputStream(args[0]);
@Cleanup
OutputStream output = new FileOutputStream(args[1]);
byte[] b = new byte[10000];
while (true) {
int r = input.read(b);
if (r == -1)
break;
output.write(b, 0, r);
}
}
}
Java Vanilla
public class CleanupDemo {
public static void main(String[] args) throws IOException {
try (OutputStream output = new FileOutputStream(args[1])) {
try (InputStream input = new FileInputStream(args[0])) {
byte[] b = new byte[10000];
while (true) {
int r = input.read(b);
if (r == -1)
break;
output.write(b, 0, r);
}
}
}
}
}
@Synchronized
@Synchronized
allows you to achieve something similar to the synchronized
keyword, but locking on different objects. The keyword locks on this
, while the annotation locks on a special private field named $lock
. If this field does not exist, it will be created by Lombok. This is the default behavior, but you can also specify lock objects yourself. When dealing with static
methods, the annotation will lock on a static field named $LOCK
. Please, consider that just like synchronized
, the annotation can only be used on static and instance methods.
With Lombok
public class SynchronizedDemo {
private final Object objectToLock = new Object();
@Synchronized
public static void sayHello() {
System.out.println("Hello!");
}
@Synchronized
public int getOne() {
return 1;
}
@Synchronized("objectToLock")
public void printObject() {
System.out.println(objectToLock);
}
}
Java Vanilla
public class SynchronizedDemo {
private static final Object $LOCK = new Object[0];
private final Object $lock = new Object[0];
private final Object readLock = new Object();
public static void sayHello() {
synchronized($LOCK) {
System.out.println("Hello");
}
}
public int getOne() {
synchronized($lock) {
return 1;
}
}
public void printObject() {
synchronized(readLock) {
System.out.println(objectToLock);
}
}
}
@SneakyThrows
@SneakyThrows
can be used to sneakily throw checked exceptions without actually declaring them in your method's throws
clause, as you normally would. So, this annotation allows you to avoid the required try-catch
blocks completely by handling all the checked exceptions quietly. Lombok will not ignore, wrap, replace, or modify the thrown checked exception. On the contrary, it will mislead the compiler. In fact, at the JVM (Java Virtual Machine) class file level, all exceptions can be thrown regardless of the throws
clause of your methods, which is why this works. This annotation can be dangerous and should be used carefully. This is why you should read this page from the Lombok official documentation to learn more about when and how to use it.
@Builder
You may need to develop a builder object allowing you to create objects by following a step-by-step construction pattern, such as Author.builder().id("1").name("Maria").surname("Williams").build();
. This is particularly useful when dealing with large classes with several fields. Instead of using a constructor with many parameters, you can use this more readable approach.
By using the @Builder
annotation, you let Lombok generate the builders for you. By annotating a class with @Builder
, Lombok will produce a class implementing the aforementioned builder pattern. For example, by annotating the Author
class, an AuthorBuilder
class will be automatically generated. Since the behavior of your builder may be complex or highly-tailored, Lombok offers many parameters to achieve the desired result. You can find out them all here.
@Log
The majority of loggers require you to set up a logger instance in every class where you want to log. This definitely involves boilerplate code. By annotating a class with @Log
, Lombok will automatically add a static final log
field, initialized as required by your logging library. This is why Lombok provides developers with an annotation per each of the most popular logging frameworks. You can find the entire list here.
The Lombok Plugin
The most popular and widely used IDEs come with an official Lombok plugin designed to help developers use Lombok. In particular, it supports you by offering shortcuts to the most common Lombok annotation. Plus, it suggests to you the annotations you may require or be looking for based on where you clicked. At the time of writing IntelliJ IDEA, Eclipse, Spring Tool Suite, (Red Hat) JBoss Developer Studio, MyEclipse, Microsoft Visual Studio Code, and Netbeans are officially supported. Follow the link related to your IDE to get support on how to install it. Visit the Lombok website for the complete list of all supported IDEs.
Is Using Lombok A Risk?
You may be concerned about spreading Lombok annotations throughout your codebase. In fact, what would happen if you decided to avoid using it? You might be finding yourself stuck. Well, this is not a real problem because Lombok comes with a delombok tool. As stated in the official documentation, although not covering all possible IDEs and cases, it makes the process of freeing your code from Lombok easier. What this tool does is auto-generate Java source code containing the same features contained in the bytecode Lombok would have injected. This way, your Lombok annotated codebase will be simply replaced with a standard, non-Lombok Java one. As a result, your entire project will no longer depend on Lombok. So, to answer the original question, no, not at all. Using Lombok does not represent a risk for the future or your project.
Conclusion
In this article, we looked at how to use Project Lombok, a Java library that automatically plugs into your editor and builds tools, to avoid writing boilerplate, boring, and repetitive code Java is well known for. As shown, this is an easy way to make you a more productive developer and do not waste your time on cumbersome activities. By starting to take advantage of its most relevant annotations, you can avoid writing thousand of lines of code with no real value from the business logic point of view of your project. Plus, there is always the possibility to make your project no longer depend on Project Lombok easily. So, it using it does not represent a risk of finding yourself locked in. In conclusion, every Java developer should use Project Lombok, and explaining everything you need to start using it is what this article was aimed at.
Thanks for reading! I hope that you found this article helpful. Feel free to reach out to me with any questions, comments, or suggestions.