close icon
JAVA

A Complete Guide to Lombok

Let’s see everything you should know to start using Project Lombok. We will have a look at how to integrate it into your IDE and use it to avoid boilerplate code.

July 29, 2021

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.

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon