Project Lombok: Reducing Java Boilerplate Code

Overview

Lombok is an open-source library that is used to reduce boilerplate code in Java classes. This is achieved by replacing many of the repetitive pieces of code with simple and concise annotations.

Lombok injects itself in the build process (via your project/IDE) and autogenerates the bytecode for the desired methods into your .class files.

So with Lombok, you can get rid of all the getter and setter methods, hashcode, and equals methods and many more just by adding annotations.

Lombok Setup

Installing Lombok into Eclipse

Download the Lombok .jar file from the official website. Run the downloaded lombok.jar file or execute the command in the terminal:

java -jar lombok.jar  

This will start the installer:

project_lombok_setup_1

If it didn't automatically detect the location of your preferred IDE, you can specify the location manually and then finish the installation by clicking 'Install/Update'.

project_lombok_setup_2

You can check whether the installation is active or not in Eclipse's "About" dialog at the end of the copyright text:

project_lombok_setup_3

Installing Lombok into NetBeans

Download the Lombok .jar from the official website and add it to the project libraries.

Activating the plugin is as easy as selecting Project Properties -> Build - Compiling -> Enable Annotation Processing in Editor.

project_lombok_setup_4 NetBeans Installation

Installing Lombok into IntelliJ

IntelliJ Idea makes it really easy to install plugins into the IDE:

  • Go to File -> Settings -> Plugins and select Browse Repositories project_lombok_setup_5

  • Search Lombok Plugin and click Install Plugin project_lombok_setup_6

After this, just restart the IDE and you're all set.

For other IDE's you can visit their home page and check the Install section.

Lombok Dependency

We need to add the following dependency in our pom.xml:

<dependency>  
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>{version}</version>
    <scope>provided</scope>
</dependency>  

Note: The scope is provided as Lombok is a pure build dependency and not a runtime one. This means that we expect the application to provide us with the dependency on runtime.

Lombok Annotations

@Getter and @Setter

These annotations can be used either at a field or class level. If used at the class level, it will generate getters and setters for all the fields in the class:

@Getter
@Setter
public class User {

    private String name;
    private String email;
}

project_lombok_setup_6

As seen in the IDE window, both of the fields now have their respective getter and setter methods, even though we didn't actually define them ourselves.

If you want getters/setters for only certain fields, annotate them accordingly:

public class User {

    @Getter
    @Setter
    private String name;
    private String email;
}

If you'd like to change the access level of the generated methods and fields, you can do so using the AccessLevel argument:

@Setter(AccessLevel.PROTECTED)
private String email;  

There are a few access levels that Lombok offers in the form of arguments:

  • MODULE
  • NONE (Represents not generating anything or the complete lack of a method)
  • PACKAGE
  • PRIVATE
  • PROTECTED
  • PUBLIC

Constructor Annotations

@AllArgsConstructor

The @AllArgsConstructor decorator will generate a public constructor will all the fields declared in your class in the same order as they are defined:

@AllArgsConstructor
public class User {

    private String name;
    private String email;
}

The generated constructor would look like:

public User(String name, String email) {  
    this.name = name;
    this.email = email;
}

@NoArgsConstructor

@NoArgsConstructor will generate a constructor with no arguments:

@NoArgsConstructor
public class User {

    private String name;
    private String email;
}

The generated constructor would look like:

public User() {}  

Note: If the constructor cannot be generated due to the presence of final fields, an error message will occur.

@RequiredArgsConstructor

@RequiredArgsConstructor will generate a constructor with all the final fields in the class:

@RequiredArgsConstructor
public class User {

    private final String name;
    private String email;
}

The generated constructor would look like:

public User(final String name) {  
    this.name = name;
}

Note: @NoArgsConstructor and @RequiredArgsConstructor can't be used together and will throw compile time error if you attempt to do so.

@EqualsAndHashCode

@EqualsAndHashCode can use used at the class level which will generate implementations for equals(Object other) and hashCode() methods.

By default, it will use all the non-static and non-transient fields:

@EqualsAndHashCode
public class User {

    private String name;
    private String email;
    private Integer age;
}

If there are certain fields you don't want to include in the equals or hashCode methods, then we can exclude those specific fields by using @EqualsAndHashCode.Exclude:

@EqualsAndHashCode
public class User {

    private String name;

    private String email;

    @EqualsAndHashCode.Exclude
    private Integer age;
}

Alternatively, we can specify fields to be included by using @EqualsAndHashCode.Include and @EqualsAndHashCode(onlyExplicitlyIncluded = true):

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {

    @EqualsAndHashCode.Include
    private String name;

    @EqualsAndHashCode.Include
    private String email;

    private Integer age;
}

@ToString

Similarly, @ToString can be used to generate the toString() implementation. By default, all non-static fields will be printed. You can specify to include or skip certain fields by using a combination of @ToString.Exclude, @ToString.Include, and @ToString(onlyExplicitlyIncluded = true) just like before:

@ToString(onlyExplicitlyIncluded = true)
public class User {

    @ToString.Include
    private String name;

    @ToString.Include
    private String email;

    private Integer age;
}

@Data

It's common to have all of the aforementioned annotations in your POJO. Rather than writing an annotation for each of them, Lombok gave provides an aggregated annotation @Data.

This bundles the features of @Getter/@Setter, @EqualsAndHashCode, @ToString and @RequiredArgsConstructor together as if you've stacked them all:

@Data
public class User {

    private final String name;
    private String email;
}

@Value

Sometimes you want your object to be immutable after their creation. @Value is an immutable variant of @Data and is used exactly for this purpose.

By default, all the fields are made final and setters are not created:

@Value
public class User {

    private String name;
    private String email;
}

@Builder

The Builder pattern is a creational design pattern that is used to help build objects in a step-by-step manner.

As you might already know, while the builder pattern does allow you to instantiate objects in a more verbose and clean way than with constructors, the underlying code needed to implement the pattern is quite convoluted.

@Builder lets you automatically produce the code required for it:

@Builder
@Data
public class User {

    private String name;
    private String email;
}

Now you can create the User object using the Builder pattern without all of the code required to support it:

    User user = new User.UserBuilder()
        .email("[email protected]")
        .name("test name")
        .build();

    System.out.println(user.getEmail());

Logging

Its common to use loggers in our application and typically we need to initialize a log variable at the top of the class and then use it in our methods.

This can be achieved by @Log which automatically creates this field:

@Log
public class Test {

    public static void main(String[] args) {
        log.severe("Log message");
    }
}

@Log creates a log variable with a java.util.logging.Logger.getLogger(LogExample.class.getName()) object.

Lombok supports other logging frameworks too, which can be used by annotations like @Log4j, @Slf4j etc.

The full list of supported frameworks can be seen here.

Writing Thread Safe Methods

In Java for multi-threaded applications, we use the synchronized keyword at critical sections of the code.

We typically use a synchronized block with an object as a lock:

public class SynchronizedJavaExample {  
    private static final Object lock = new Object();

    public static void test() {
        synchronized (lock) {
            System.out.println("test");
        }
    }
}

This can be auto-generated using the @Synchronized keyword:

public class SynchronizedExample {

    @Synchronized
    public static void test() {
        System.out.println("test");
    }

}

Conclusion

In this article, we've provided an introduction to Project Lombok and saw how it eases up our development process by reducing boilerplate code with simple annotations. This drastically enhances readability and brevity.

The code for the examples used in this article can be found on Github.