/ clean code

To Lombok, or not to Lombok

If you are reading this, chances are that you already know a little about Project Lombok. Perhaps you love it and is looking for additional information to support your righteous claim that Lombok should be used and embraced by everyone in your team.
Perhaps you are the other side of the coin, believing that Lombok is a potential liability that could compromise the project by having an additional dependency, and that it hides certain code behind annotation. Perhaps you simply do not wish everyone to spend time learning something new, or do not wish to force it upon someone else.

What is Lombok

The official description of Project Lombok is as followed:

Project Lombok is a Java library that automatically plugs into your editor and build tools, spicing up your Java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

In short, this is a compile time dependency allow you to replace common boilerplate code with certain useful annotations.

When to use Lombok

While Lombok provides you with plentiful of neat annotated features, I will only highlight the most commonly used ones throughout this example. I will do my best to update this list, if Lombok changes in the future - so don't hesitate to PM or comment so that we may see it.

For this to work, you need to have a Lombok Plugin installed in your IDE.
For IntelliJ you can search for Lombok Plugin to find and install it. This will allow your IDE to understand Lombok and compile the code.

Let us walk through an example. We all know these boilerplate classes that contain only getters, setters, toString, equals, and hashCode:

import java.util.Objects;

public class Project {

  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;

  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 getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public String getStatus() {
    return status;
  }

  public void setStatus(String status) {
    this.status = status;
  }

  public String getState() {
    return ":" + state;
  }

  public void setState(String state) {
    this.state = state;
  }

  public int getPriority() {
    return priority;
  }

  public void setPriority(int priority) {
    this.priority = priority;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Project project = (Project) o;
    return priority == project.priority &&
        Objects.equals(id, project.id) &&
        Objects.equals(name, project.name) &&
        Objects.equals(description, project.description) &&
        Objects.equals(status, project.status) &&
        Objects.equals(state, project.state);
  }

  @Override
  public int hashCode() {
    return Objects.hash(id, name, description, status, state, priority);
  }

  @Override
  public String toString() {
    return "Project{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", description='" + description + '\'' +
        ", status='" + status + '\'' +
        ", state='" + state + '\'' +
        ", priority=" + priority +
        '}';
  }
}

Did you see it? In one of the getters we have custom logic that is easily overlooked.

After we replace all the getter and setter methods with Lombok annotations @Getter and @Setter, it looks like this:

import java.util.Objects;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Project {
  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;
  

  public String getState() {
    return ":" + state;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Project project = (Project) o;
    return priority == project.priority &&
        Objects.equals(id, project.id) &&
        Objects.equals(name, project.name) &&
        Objects.equals(description, project.description) &&
        Objects.equals(status, project.status) &&
        Objects.equals(state, project.state);
  }

  @Override
  public int hashCode() {
    return Objects.hash(id, name, description, status, state, priority);
  }

  @Override
  public String toString() {
    return "Project{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", description='" + description + '\'' +
        ", status='" + status + '\'' +
        ", state='" + state + '\'' +
        ", priority=" + priority +
        '}';
  }
}

That's a little better, right? You can clearly see that we have one getter that does a little more than you would normally expect it to do.

Keep in mind that the compiled code will contain all the getters and setters as you would expect, and that the custom getState method will remain untouched.

There is still room for improvement. We still have those generic equals and hashCode methods. Let us annotate them away, after making sure there is nothing extra going on in there.

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@EqualsAndHashCode
public class Project {
  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;


  public String getState() {
    return ":" + state;
  }

  @Override
  public String toString() {
    return "Project{" +
        "id=" + id +
        ", name='" + name + '\'' +
        ", description='" + description + '\'' +
        ", status='" + status + '\'' +
        ", state='" + state + '\'' +
        ", priority=" + priority +
        '}';
  }
}

That's better! You can now clearly see that we are using @Getter, @Setter, and an @EqualsAndHashCode annotation. What these annotation does should be obvious by the naming used.

If you wish to exclude certain variables, you can set them explicitly in the code, or add the @Getter and @Setter annotation on the individual variables you want to use.

We still have the toString method. This does not have any special formatting for printing out the values; so we can replace this with the Lombok annotation @ToString.

@Getter
@Setter
@ToString
@EqualsAndHashCode
public class Project {
  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;


  public String getState() {
    return ":" + state;
  }
}

I cannot speak for you, but to me this class is extremely easy to read and understand. We have getters, setters, toString, and the equals and hashCode in this class, and we have a single getter that does something extra.

But wait, there is more. We are using a lot of annotations for this class. Well, perhaps not a lot, but more than we need. Let us replace all these annotations with the @Data annotation!

import lombok.Data;

@Data
public class Project {

  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;


  public String getState() {
    return ":" + state;
  }
}

The @Data annotation does several things. It provides the functionality of @Getter, @Setter, @ToString, @EqualsAndHashCode and @RequiredArgsConstructor.

We will look at the different constructor annotations later below.

Make logging easier

Earlier I mentioned that Lombok has a neat annotation for Logging.
What if we wanted to log out the state every time it was called?
It would look like this:

import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Data
public class Project {

  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;

  private Logger log = LoggerFactory.getLogger(Project.class);

  public String getState() {
    String state = ":" + this.state;
    log.debug(state);
    return state;
  }
}

By using a single @Slf4j (or @Log4j) annotation it would look like this:

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Data
@Slf4j
public class Project {

  private Long id;
  private String name;
  private String description;
  private String status;
  private String state;
  private int priority;


  public String getState() {
    String state = ":" + this.state;
    log.debug(state);
    return state;
  }
}

If you use logging in your projects, you have more than likely at some point forgot to update the class name in your Logger.

Most, if not all of these annotations completely takes out the potential human errors of writing boilerplate code. Allowing you and your co-workers to focus on what is important.


Other useful annotations

There are several other annotations that are absolutely worth mentioning.

@NoArgsConstructor

This provides an empty constructor. If you wanted to make a private empty constructor, you can do that as well @NoArgsConstructor(access = AccessLevel.PRIVATE).

@RequiredArgsConstructor

This will provide you with a constructor with all the final and non-null fields.
and @AllArgsConstructor


Reasons for not using Lombok

As with any library, dependency, or pretty much anything in life; there will be those that are opposed to things. If you want reasons for not using Lombok, it would most likely be one or more of the following:

- You have to learn something new

If you are a developer, and do not wish to continue learning, you are more than likely in the wrong profession. As a developer, you are expected to keep learning while staying relevant with technologies as it advances.

- If we someday decide to stop using Lombok, it would take a long time to undo it

Lombok has a Delombok tool that can be run on your project. This will replace all annotations with the actual code.

- Everyone that is going to work on the project have to install a Lombok Plugin to work on the project

This is probably the most valid point against the use of Lombok.
If you have users in your team that use a custom IDE (or using notepad, VIM, or something else silly-willy) it may be difficult to work with projects that use Lombok.
That being said, both IntelliJ, Eclipse and Netbeans support the use of Lombok, and even if you don't intend to use it yourself, you should install it so your team may use it at its fullest.

If you are using an IDE that do not support Lombok, please comment below.

Introducing Lombok to your Team

Okay, so you are convinced that Lombok is the correct choice. How would you go about convincing your team to try it out?

Provide sample code

Find a class that has a lot of getters and setters.
Find a class that contains a constructor that takes in all fields.
Lombokify it! Show the team how incredibly easy it is to read the class without all the horrific and useless getters and setters.
We don't need to read all the getters and setters unless it does something extra!

Demonstrate the most useful annotations

  • @Getter
  • @Setter
  • @ToString
  • @EqualsAndHashCode
  • @Data
  • @NoArgsConstructor
  • @RequiredArgsConstructor
  • @AllArgsConstructor
  • @SLf4j
  • @Log4j / @Log4j2

Ask them if they can guess what each if these fields do. Luckily, most of these are self-explanatory.

The annotations you should spend some extra time explaining are:
@Data is just a collection of the previously mentioned annotations.
@RequiredArgsConstructor only includes the final and non-null fields.
@AllArgsConstructor includes all non-null fields.
@Slf4j provides an invisible log variable that can be used and accessed to write logs without having to setup a Logger manually in each class.

Super easy!


A conclusion?

What we really are learning from this, is that Java still is a flawed language. It is quickly becoming "ye olde" programming language, and will probably be left behind unless it can keep up with languages such as Kotlin.
After all, we want to code. We don't want to read all the generic unmodified getters and setters.
We want the code to be easy to read and understand.

Meanwhile, I recommend that you try out Project Lombok in your projects.
I promise you will love and embrace it once you get used to reading it.


Find more info and annotations at https://projectlombok.org/