How to use Lombok with Java

Writing code also comes with a responsibility. Each and every line of code we write increases the coding time, size of the source and mostly it will introduce more bugs in to the code. As a developer you should avoid writing unnecessary codes and always try to write codes in most efficient way. Also it’s important to avoid writing codes which can be automatically generate for you, which we are going to talk about in this article.

Java is a programming language which consider as a very verbose language. To write a simple functionality we will have to use many unnecessary codes.

Project Lombok is a one of the library which eases the pain of writing more codes. It is mainly an open source library to reduce boilerplate code in java classes. Lombok is used with annotations and it can be applied at class level, method level, constructor level and even at the variable level.

How Lombok works?

Since Lombok works based on annotations, developers just have to add the correct annotation based on the situation. At first you may be see it as an overhead for the programmer. But it’s not. There is nothing to worry about runtime performance when you are using the Lombok, because it’s generates code at compile time. Also when comes to IDE Integrations there are nothing to be worried about because there are plugins for almost every IDE which uses for Java developments.

Let’s start with the mostly used annotations with Lombok

  1. @Getter and @Setter


These two are the mostly used annotations with Lombok. With @Getter and @Setter you can get and set values without implementing the getters and setters manually in POJO classes. You can use those annotations for whole class at once or you can set it for required variables. When we defined those annotations, on class level it’ll create getters and setters for whole variables in the class. To avoid generating getters or setters you can use @Tolerate to already implemented getter or setter.

@Getter
@Setter
public class Person {
    private String firstName;
    private String lastName;
    private int age;

    @Tolerate
    public String getLastName(String firstName) {

        if(firstName == null)
        {
            return "INVALID";
        }
        else
        {
            return lastName;
        }
    }
   
}

2. @ToString

Using this annotation on class level it will override default toString() method and Lombok will generate the toString() method for all the non static variables.

 @Override
    public String toString() {
        return "Person{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", age=" + age +
                '}';
  }

Lombok will generate above code toString() at runtime.

3. @NonNull

NonNull annotation will add a null check for the parameter before using it on any statement. if the parameter is null it will throw a NullPointerException. Consider a situation where you are designing an API, and the parameter is marked as @NonNull. When the corresponding JSON hits the API it will throws a NullPointerException at controller level instead of executing the whole code lines

public class PersonInfo {
    private String name;

    public getPersonName(@NonNull Person person) { 
	/*
	  Will throw a NullPointerException if person is null
	*/
        this.name = person.getName();
    }
}

4. @Cleanup

When we use resources like Readers, InputStream or any other resources which required cleaning up of the resource with try/finally block can be handled by this annotation.

public class ExampleCleanUp {
    public static void main(String[] args) throws IOException {
        InputStream in = new FileInputStream(args[0]);
        OutputStream out = new FileOutputStream(args[1]);

        try {
            byte[] b = new byte[10000];
            while (true) {
                int r = in.read(b);
                if (r == -1) break;
                out.write(b, 0, r);
            }
        } finally {
            out.close();
            in.close();
        }
    }
}

Above code can be handled as below

public class CleanupExample {
  public static void main(String[] args) throws IOException {
   @Cleanup InputStream in = new FileInputStream(args[0]);
   @Cleanup OutputStream out = new FileOutputStream(args[1]);
   byte[] b = new byte[10000];
   while (true) {
      int r = in.read(b);
      if (r == -1) break;
      out.write(b, 0, r);
   }
  }
}

5. @Data

This annotation will take care of the @Getter, @Setter, @ToString, @RequiredArgsConstructor and @EqualsAndHashCode annotations. In other words, this generates all the boilerplate code associated with POJO and beans.

import java.util.Arrays;

public class StudentExample {
  private final String fullName;
  private int age;
  private double score;
  private String[] tags;
  
  public StudentExample(String fullName) {
    this.fullName = fullName;
  }
  
  public String getFullName() {
    return this.fullName;
  }
  
  void setAge(int age) {
    this.age = age;
  }
  
  public int getAge() {
    return this.age;
  }
  
  public void setScore(double score) {
    this.score = score;
  }
  
  public double getScore() {
    return this.score;
  }
  
  public String[] getTags() {
    return this.tags;
  }
  
  public void setTags(String[] tags) {
    this.tags = tags;
  }
  
  @Override public String toString() {
    return "StudentExample(" + this.getFullName() + ", " + this.getAge() + ", " + this.getScore() + ", " + Arrays.deepToString(this.getTags()) + ")";
  }
  
  protected boolean canEqual(Object other) {
    return other instanceof StudentExample;
  }
  
  @Override public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof StudentExample)) return false;
    StudentExample other = (StudentExample) o;
    if (!other.canEqual((Object)this)) return false;
    if (this.getFullName() == null ? other.getFullName() != null : !this.getFullName().equals(other.getFullName())) return false;
    if (this.getAge() != other.getAge()) return false;
    if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
    if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
    return true;
  }
  
  @Override public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final long temp1 = Double.doubleToLongBits(this.getScore());
    result = (result*PRIME) + (this.getFullName() == null ? 43 : this.getFullName().hashCode());
    result = (result*PRIME) + this.getAge();
    result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
    result = (result*PRIME) + Arrays.deepHashCode(this.getTags());
    return result;
  }
  
  public static class Exercise<T> {
    private final String fullName;
    private final T value;
    
    private Exercise(String fullName, T value) {
      this.fullName = fullName;
      this.value = value;
    }
    
    public static <T> Exercise<T> of(String fullName, T value) {
      return new Exercise<T>(fullName, value);
    }
    
    public String getFullName() {
      return this.fullName;
    }
    
    public T getValue() {
      return this.value;
    }
    
    @Override public String toString() {
      return "Exercise(fullName=" + this.getFullName() + ", value=" + this.getValue() + ")";
    }
    
    protected boolean canEqual(Object other) {
      return other instanceof Exercise;
    }
    
    @Override public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof Exercise)) return false;
      Exercise<?> other = (Exercise<?>) o;
      if (!other.canEqual((Object)this)) return false;
      if (this.getFullName() == null ? other.getValue() != null : !this.getFullName().equals(other.getFullName())) return false;
      if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false;
      return true;
    }
    
    @Override public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      result = (result*PRIME) + (this.getFullName() == null ? 43 : this.getFullName().hashCode());
      result = (result*PRIME) + (this.getValue() == null ? 43 : this.getValue().hashCode());
      return result;
    }
  }
}

Above whole code can be handled as below

import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
import lombok.ToString;

@Data public class StudentExample {
  private final String fullName;
  @Setter(AccessLevel.PACKAGE) private int age;
  private double score;
  private String[] tags;
  
  @ToString(includeFieldNames=true)
  @Data(staticConstructor="of")
  public static class Exercise<T> {
    private final String fullName;
    private final T value;
  }
}

6. @Builder

With this annotation Lombok modifies the POJO class by adding a static subclass with builder design pattern. Builder design pattern uses to develop complex objects without changing the structure.

As an example, consider the Person object. A person object has mandatory field’s personId, firstName, lastName and three optional fields. Let’s say we don’t need to change the state of the person object once it’s created. Therefore it’s recommended to implement an immutable person class. So to achieve that, all members have to be final.

public class PersonVanilla {

    private final String firstName;  //mandatory
    private final String lastName;  //mandatory
    private final String personId;  // mandatory
    private final String optionalField1;  //optional
    private final String optionalField2;  //optional
    private final String optionalField3;  //optional
}

To initialize the members, it’s required to have an allArgsConstructor. Imagine a situation where you need to create a person object with mandatory and optional fields. For each and every those combination we will need to have a constructor

Assume you need to add a mandatory field called “birthDate”. To add this you will have to change every single constructor. This is where the builder pattern comes in to the scene

public class PersonVanilla {


    private final String firstName;  //mandatory
    private final String lastName;  //mandatory
    private final String personId;  // mandatory
    private final String optionalField1;  //optional
    private final String optionalField2;  //optional
    private final String optionalField3;  //optional

    public PersonVanilla (PersonVanillaBuilder personVanillaBuilder)
    {
        this.personId = personVanillaBuilder.personId;
        this.firstName = personVanillaBuilder.firstName;
        this.lastName = personVanillaBuilder.lastName;
        this.optionalField1 = personVanillaBuilder.optionalField1;
        this.optionalField2 = personVanillaBuilder.optionalField2;
        this.optionalField3 = personVanillaBuilder.optionalField3;
    }

    public static class PersonVanillaBuilder
    {
        private final String firstName;  //mandatory
        private final String lastName;  //mandatory
        private final String personId;  // mandatory
        private String optionalField1;  //optional
        private String optionalField2;  //optional
        private String optionalField3;  //optional

        public PersonVanillaBuilder (String firstName, String lastName, String personId)
        {
            this.personId = personId;
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public PersonVanillaBuilder optionalField1 (String optionalField1)
        {
            this.optionalField1 = optionalField1;
            return this;
        }

        public PersonVanillaBuilder optionalField2 (String optionalField2)
        {
            this.optionalField2 = optionalField2;
            return this;
        }

        public PersonVanillaBuilder optionalField3 (String optionalField1)
        {
            this.optionalField3 = optionalField3;
            return this;
        }

        public PersonVanilla build()
        {
            PersonVanilla personVanilla = new PersonVanilla(this);
            return personVanilla;
        }
    }
}

Above code will be change as below with @Builder annotation

@Builder
public class PersonLonbok {

    private final String firstName;  //mandatory
    private final String lastName;  //mandatory
    private final String personId;  // mandatory
    private final String optionalField1;  //optional
    private final String optionalField2;  //optional
    private final String optionalField3;  //optional
}

7. @NoArgsConstructor

This annotation will create an empty constructor for the class. However keep remember that if all the variables are define as final then it will throw a compilation error. To handle this you can force Lombok to initialize final variables by adding @NoArgsConstructor(force=true)

8. @AllArgsConstructor

@AllArgsConstructor annotation will create one parameter for every field in the POJO class

9. @RequiredArgsConstructor

This annotation creates a constructor with one parameter for each variable which requires special handling. Also all non initialized final variables get a parameter,
Also the fields generate as @NonNull will not be initialize at the declared place. For all the @NonNull fields, null check will be generate. The constructor will throw a NullPointerException if any of the parameters intended for the fields marked with @NonNull contain null

10. @UtilityClass

This annotation will make all methods static and variables in the class are final static constants. Once you annotate a class with @UtilityClass, it will generate a private constructor and no any method callers will be able to create new instance out of the utill class

How to add Lombok plugin into Intelij

Click on File -> Settings

Click on plugins and search for Lombok on the plugin screen. Select Lombok plugin and click on install

After install complete, click on Restart IDE

Leave a Reply

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