Reduce boilerplate code using Lombok plugin

One of the things I hate about Java is the ceremony around functionality. When you want to create a simple Entity class, you need to write the setters, getters, and the toString methods. This makes a small class looks like a handful. The code that we repeat over and over for the sake of ceremony is referred to as boilerplate code and most modern languages like Scala and Go have done a better job at handling this. Java has also made good progress in the recent JDK releases, but still, the boilerplate code is there to stay for some time. In this post, I will be discussing a library that avoids this boilerplate code in Java and helps make code concise and clutterless. We will be using the Lombok library that uses bytecode level manipulation to hide the boilerplate code without losing on functionality.

What is Lombok?

As I specified in the introduction, Lombok is a Java library that injects the boilerplate code into IDE and the bytecode. This not only allows us to reduce the code that we need to write but also provides a lot of convenient methods. Lombok works by replacing the common boilerplate methods using annotations.  You can read more about the Lombok project in the below official link.

For our purpose, we will be able to replace the boilerplate code for the following functionalities with Lombok annotations:

  1. Getters & Setters for class fields
  2. ToString method
  3. equals and hashcode generation
  4. Logging
  5. Variants of constructors ( No arguments, All arguments etc )

You can see Lombok in action on the following project repository. All the examples and screenshots used are from the below project.

Using Lombok

Lombok can be added to your project using dependency or including the lombok.jar file in the classpath. For a maven project, you can include the following dependency in the pom.xml file.

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

This will allow us to use the annotations ( which we will discuss in detail in the coming section ). We also need to install a Lombok plugin in the IDE to fully support the code completion and to get rid of the errors like missing methods.

Enabling Lombok support in IntelliJ

IntelliJ supports Lombok using a plugin. You can enable the plugin by going to Settings -> Plugins -> Browse repositories -> Search for Lombok

Install the plugin and you would need to restart the IDE to get this into effect.

If you are using IntelliJ 2016 or below, you may need to enable annotation processing in the settings as well. You can enable it by going to Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors  and check the box “Annotation Processing”

How does Lombok reduce the boilerplate code?

Before we see the details of the different annotations, let’s see what we are talking about. Below you have a Customer.java entity class with and without Lombok.

Customer.java ( Without Lombok )

@Entity
public class Customer implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String mobile;

    private String email;

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getEmail() {
        return email;
    }

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

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", mobile='" + mobile + '\'' +
                ", email='" + email + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Customer customer = (Customer) o;

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

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

Customer.java ( with Lombok annotations )

@Entity
@Data
public class Customer implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String mobile;

    private String email;

}

You can see how the single @Data annotation replaces the getters, setters, toString, equals & hashcode methods. Now the code looks clean and clutterless. And when I try to autocomplete on customer object, the IDE will list the methods as below.

Annotations supported by Lombok

Let’s see the supported annotations for Lombok in detail.

@Setter & @Getter

This will generate the getters and setters for the fields specified in the class. The generated methods would follow the proper conventions for the setter and getter like for boolean it would be starting with isFoo etc. You can also specify @Getter and @Setter annotation over the individual fields and the annotations also accept an attribute called AccessLevel which allows you to specify the access level for the methods generated.

If there is already a getter or setter method defined for a field, the generation logic will ignore the field and use the provided implementation.

@ToString

This will generate the implementation of the toString method and will by default include all the non-static fields in the form of a name-value pair. There some additional attributes supported by the annotation using which you can exclude or include the fields to be displayed in the generated toString method.

@EqualsAndHashcode

This will generate the implementation for the equals() and hashCode() methods. By default, the included fields would be the non-static and non-transient fields. You can use the “of” or “exclude” parameters of the annotation to control what fields are included in the implementation.

@Data

In most of the entity classes, we may require to use all the above annotations and including them on all the classes by itself is a boilerplate pattern. So to avoid this, Lombok provides a @Data annotation which is like using all the above annotations on the class. This is the one used in the above example code and this will generate setters, getters, toString, equals and hashcode. Though the @Data annotation is convenient, you may lose the flexibility to control the individual annotations. In such cases, you can annotate a particular field with specific annotation and override the behaviour.

@Slf4j

This is one very common annotation I personally use. When a class is annotated with @Slf4j, this will provide a logging mechanism wired for that class. We don’t need to write the declaration for the logger with the class name and can access by just calling log. You can see in the below screenshot that I have annotated the CustomerServiceImpl class with @Slf4j and this provides me with log instance that I can refer inside the class.

Without @Slf4j, I would need to write the following line for initializing a log instance.

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CustomerServiceImpl.class);

Lombok supports different logging libraries other than Slf4j. You could use following based on the library you are using:

  • @Slf4j : Uses Slf4j logging library
  • @Log : Supports java.util.logging
  • @CommonsLog: Provides log instance from apache.commons.logging
  • @Log4j : Provides log instance for org.apache.log4j

Constructor annotations

Let’s see how we can reduce the code written for constructors for a class. We can use 3 different annotations

@NoArgsConstructor

This will generate a constructor with no parameters. If there are final fields present, this will result in a compiler error.

@RequiredArgsConstructor

This will generate a constructor with fields requiring special handling. This will include the fields defined as final, and also the field that is marked with @NonNull and not initialized.

@AllArgsConstructor

This generates a constructor with all the fields specified in the class.

You may specify any or all of the annotations on a class.

Conclusion

You have seen that Lombok provides a lot of convenient methods for handling boilerplate code. But before we wrap up the post, I would like to give a word of caution that there are a certain group of developers that do not promote the use of Lombok. The reasoning being that the annotations should not be used in a way the codebase will be unusable when they are removed. This is a valid point in the case of Lombok as the generated methods disappear when the annotations are removed.

But you always have the choice to use or not use Lombok. I consider them as a convenience and can live with the annotations.

Let me know your thoughts and queries in the comments section.

regards
Microideation

You may also like...

2 Responses

  1. October 12, 2018

    […] lombok : This is used to reduce the boilerplate code ( getters, setters toString etc ). You can read more about the Lombok here. […]

  2. November 1, 2021

    […] as an ENUM and put under the dictionary package. I have also used the @Builder,@Data annotations of Lombok plugin for reducing some boilerplate […]

Leave a Reply

Your email address will not be published. Required fields are marked *