Reduce Java's boilerplate code using Project Lombok

I love Java, but there are few things that I hate about it. One of them is it’s verbosity. You have to write a lot of code to achieve what you could achieve with almost half or fewer lines of code in other functional/scripting languages.

There are many justifications for it’s verbosity -

  1. Java’s verbosity makes it more expressive and understandable.
  2. It improves readability of code.
  3. It makes Java programs easier to debug.
  4. Insert more justifications here…

While all of the above justifications are true, the amount of boilerplate code required to be written to do even minimal jobs can be very annoying.

Consider the following example of a Plain Old Java Object (POJO) -

public class User {
    private Integer id;
    private String name;
    private String email;
    private String phone;
    private Date dateOfBirth;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }
}

This is a small class with only five fields, but we had to write almost 50 lines of code for something so simple. Most of the code in the above class was required to write getters and setters. Now, imagine hundreds of similar data classes in your project and imagine the number of lines of code that has to be written just to provide getters and setters.

Getters, Setters, toString, hashCode methods are something which are alike in almost all your data classes, So there must be a better way to handle these common constructs right?

Enter Project Lombok! It is an honest attempt to reduce Java’s boilerplate code using simple set of annotations.

How Project Lombok helps in reducing boilerplate code?

Project lombok works like magic from the outside. It provides you with a series of annotations that you can use in your classes. It, then generates and injects code into your classes at compile time by processing these annotations.

Let’s make the above User class boilerplate-free by using Lombok’s annotations -

@Getter @Setter
class User {
    private Integer id;
    private String name;
    private String email;
    private String phone;
    private Date dateOfBirth;
}

The annotations @Getter and @Setter will automatically generate the default getters and setters for all the fields in your class. Awesome! isn’t it?

Well! At this point, you might say that what’s the need for all this when any good IDE can generate getters and setters for you?

Yeah! But, think about it, the code for getters and setters will still lie around your class files and you will have to manage this code. Also, whenever you change or add a new property, you will need to modify existing getters/setters or add new ones.

But, with lombok, you won’t need to do any of it. You can just write your data classes and forget about other boilerplates.

Installation and Usage

Lombok is available as a single jar file on the Project’s website. Download lombok.jar and follow the instructions here to install and use it in your development environment -

Usage with javac

Lombok works out of the box with javac. Just add it in the classpath while compiling your program and you’re ready to go!

javac -cp lombok.jar MyApplication.java

Installing and using Lombok in eclipse

lombok.jar comes with an installer. Just double click the jar, the installer will open which will automatically detect all the supported IDE’s in your machine -

Lombok Installer I have eclipse installed in my machine and the installer has detected it. Click Install/Update to install lombok.jar in eclipse.

Note however that lombok.jar will still need to be added in the classpath of any projects that will use lombok annotations. If you’re using maven, you can simply add the following dependencies in the pom.xml file to include lombok -

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.16</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

For gradle, add the following to your build.gradle file

compileOnly "org.projectlombok:lombok:1.16.16"

If you don’t use any build system in your project, do the following to add lombok in classpath -

Right click your project in eclipse -> Build Path -> 
Configure Build Path -> Libraries -> 
Add External JARs -> choose lombok.jar

Installation in IntelliJ Idea

Lombok doesn’t have direct support for intellij idea. But there is a plugin that adds support for most features. To install lombok plugin in intellij -

Open the Settings dialog (Command + , in mac, Ctrl + Alt + S in windows) -> 
Plugins -> Browse Repositories -> 
Search for Lombok -> click Install

IntelliJ Lombok Plugin

That’s it. Now you can use lombok in your project. Don’t forget to add lombok dependencies as described above in your pom.xml or build.gradle file.

Lombok Annotations

In this section I’ll discuss about different annotations provided by lombok for reducing boilerplate code. You can choose to use the annotations that fits your need -

1. @Getter and @Setter

Lombok’s @Getter and @Setter annotations are used to generate the default getter and setter methods for the instance variables of a class.

These annotations can be applied at field level, or at class level. If you apply the annotations at class level, getters and setters will be generated for all the non-static fields of your class.

Let’s see an example -

class User {
    @Getter private int id;
    @Getter @Setter private String name;    
}

The above lombok annotated code is equivalent to the following Java code -

class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }    

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

The generated getter and setter methods will be public by default, but, you can modify this behaviour by specifying an AccessLevel (PRIVATE, PROTECTED, PACKAGE, PUBLIC, or NONE) with the annotation -

class User {
    @Getter @Setter(AccessLevel.PROTECTED) private String email;    
}

Getter/Setter generation for any field can be disabled manually by using the special AccessLevel.NONE access level. This lets you override the behaviour of @Getter, @Setter annotation on a class -

@Getter @Setter
class User {
    @Setter(AccessLevel.NONE) private int internal_code;    
}

Getters and Setters will not be generated if there is already a method with the same name and parameters in your class.

2. @NonNull

@NonNull annotation is used to generate null-checks for the annotated fields. If it is used on an instance variable, all the setter methods, or constructors generated by lombok will contain a null-check for that instance variable.

If the @NonNull annotation is used on the parameter of a method or constructor, a null-check for that parameter will be inserted inside the method or constructor.

The null-check looks like - if(param == null) throw new NullPointerException("param");. It is inserted at the very beginning of your method. For constructors, it is inserted immediately after any this() or supper() calls.

Note that, if a null check is already present at the top of a method or constructor, it will not be generated by lombok. Let’s consider a simple example -

class User {
    @Getter @Setter @NonNull private String name;
}

The above annotated code is equivalent to the following java code -

class User {
    @Getter @Setter @NonNull private String name;

    @NonNull
    public String getName() {
        return name;
    }

    public void setName(@NonNull final String name) {
        if(name == null) throw new NullPointerException("name");
        this.name = name;
    }
}

3. @ToString

Add @ToString annotation to your classes to generate an implementation of toString() method. The default implementation will print your class name along with the value of each fields separated by commas -

@ToString
class Rectangle {
    private int width;
    private int height;
}

Following is the equivalent Java Code -

@ToString
class Rectangle {
    private int width;
    private int height;

    @Override
    public String toString() {
        return "Rectangle(" + width + "," + height + ")";
    }
}

If you want to include field names as well in the toString() method, use @ToString(includeFieldNames=true).

By default, all the non-static fields of the class will be printed. If you want to exclude some fields from toString(), use @ToString(exclude="fieldName"). Moreover, you can specify exactly which fields to include in toString() using - @ToString(of={"field1", "field2"})

Also, you can use @ToString(callSuper=true) to include the output of the superclass implementation of toString() to the output.

@ToString(includeFieldNames=true, exclude={"id", "dept"})
class Employee {
    private int id;
    private String name;
    private String email;
    private String dept;
}

4. @EqualsAndHashCode

You can annotate a class with @EqualsAndHashCode to generate implementations of equals() and hashCode() methods. By default, it will use non-static and non-transient fields, but you can exclude more fields using exclude parameter, or, you can include selected fields using of parameter.

Also, like @ToString, you can use callSuper=true with @EqualsAndHashCode as well. This will cause lombok to call the equals/hashCode method of super class before considering fields in the current class.

@EqualsAndHashCode(exclude={"dept", "address"})
class User {
    private String id;
    private String name;
    private String email;
    private String dept;
    private Address address;
}

And following is the equivalent Java code -

class User {
    private String id;
    private String name;
    private String email;
    private String dept;
    private Address address;
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (id != null ? !id.equals(user.id) : user.id != null) return false;
        if (name != null ? !name.equals(user.name) : user.name != null) return false;
        return email != null ? email.equals(user.email) : user.email == null;
    }

    @Override
    public int hashCode() {
        int result = id != null ? id.hashCode() : 0;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (email != null ? email.hashCode() : 0);
        return result;
    }
}

5. @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor

@NoArgsConstructor will generate a constructor with no parameters. If this is not possible, typically, because of final variables which must be initialized, compiler will generate an error. If @NoArgsConstructor(force=true) is used then all the final variables will be initialized with 0/false/null.

@NoArgsConstructor(force=true)
class User {
    private final Integer id = 0;
    private String name;
}

The above lombok annotated code, after processing looks like -

@NoArgsConstructor(force=true)
class User {
    private Integer id = 0;
    private String name;

    User() {

    }
}

@RequiredArgsConstructor generates a constructor with all un-initialized final fields and @NonNull fields. An explicit null check is also generated for fields marked with @NonNull -

@RequiredArgsConstructor
class User {
    private final Integer id = 0;
    @NonNull private String name = "Unknown Member";
    private String department;
    private final String email;
    @NonNull private String country;
}

For the above class, fields id, name and department are not included in the generated @RequiredArgsConstructor because id and name are already initialized and department is neither final nor annotated with @NonNull. -

@RequiredArgsConstructor
class User {
    private final Integer id = 0;
    @NonNull private String name = "Unknown Member";
    private String department;
    private final String email;
    @NonNull private String country;

    User(String email, @NonNull String country) {
        if(country == null) throw new NullPointerException("country");
        this.email = email;
        this.country = country;        
    }
}

@AllArgsConstructor generates a constructor with all the fields in your class. Also, null-checks are added for fields marked with @NonNull.

6. @Data

@Data annotation is the combination of @Getter, @Setter, @ToString, @EqualsAndHashCode and @RequiredArgsConstructor annotations. It lets you generate all the boilerplate code that is normally associated with a Plain Old Java Object (POJO) with just a single annotation.

Conclusion

We explored the most commonly used annotations provided by lombok. However, there are a lot of other useful annotations available in the library. Some of my favourites are -

I urge you to have a look at the Lombok’s Feature Page to explore these annotations as well. Thank you for reading my blog. Please ask any doubts or clarifications in the comment section below.