Spicing up your Java — Introduction to Project Lombok
By: Somnath Musib
Tags: Java

Spicing up your Java — Introduction to Project Lombok

Project Lombok is a java library that automatically plugs into your editor and the build tools to assist with some of the boilerplate code, thus spicing up your Java

Overview

Java is no doubt one of the most dominating programming languages of all time. However, it is often criticized for its verbose nature. For example, in the classic JavaBeans style, we manually need to add /generate no-arg/all-args constructors and getter, setters, toString for our properties. Though most modern IDEs provide an automated way to generate these boilerplate codes from the properties, it is still a manual activity.

In this article, we will shed some light on project Lombok. It is a Java library that automatically plugs into the editor and to the build tools to generate these boilerplate codes for us with the help of annotations.

Setting Up

Download

Browse to https://projectlombok.org/download and download the Lombok jar.

Configure Lombok in Eclipse and Spring Tool Suite

Once downloaded, double click on lombok.jar. This will open up the following window:-

Lombok configuration for IDE

Initially, it will try to scan the IDEs and automatically list down the IDEs as shown below:-

Lombok configuration (Image Courtesy: https://projectlombok.org/setup/eclipse)

In case if does not list down, click Specify location option and select your IDE and click Install/Update:-

IDE Path selected

Post-installation, we should be viewing the following screen:-

Lombok installed successfully

Configure Lombok in Intellij IDEA

Lombok uses the Java annotation processing tool (APT) to process the annotations. However, in IDEA annotation processing is not enabled by default and requires to be configured.

Create a new maven project in IDEA and do the following:-

Browse to Settings > Build, Execution, Deployment > Compiler > Annotation Processors and select your project Do the following:-

a. Check Enable annotation processing box

b. Select Obtain processors from project classpath

3. Click Plugins and select Marketplace and search lombok

Click Lombok. And then click Install and then click Restart IDE. This will install the Lombok plugin in IDEA.

Maven project set up

Now that we have installed Lombok, we need to add lombok.jar in our classpath. We will use Lombok maven dependency in our maven project. In case you want to continue with a traditional Java project, add lombok.jar in the classpath. In this article, we will use Maven.

Create a new maven project and add the following dependency:-

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<version>1.18.10</version>

<scope>provided</scope>

</dependency>

Deep Dive into Lombok

@Getter and @Setter

Lombok generated getter and setters if there are @Getter and @Setter annotations in the class definition.

In the following example, we have created two properties and added @Getter and @Setter annotations in the class.

Person.java

In the main class, we can access the get* and set* methods.

GetterSetterMain.java

@ToString

It generates a toString method comprises of all non-static fields. By default, it prints class name along with each field in order separated by commas. To be more specific about the fields in toString method, we can use @Include and @Exclude annotations. We can also use callSuper attribute to specify to include superclass toString.

And the main class:-

Following is the output:-

Let us now extends to the superclass and see the usage of callSuper attribute:

Super class Box.java

We have updated ColoredBox.java to add callSuper attribute:-

ColoredBox.java

ToStringMain.java

package io.codefountain.project.lombok.tostring;



public class ToStringMain {



public static void main(String[] args) {



ColoredBox box = new ColoredBox();

box.setHeight(1.5);

box.setWidth(2.5);

box.setColor("Green");



System.out.println(box.toString());

}

}

Below is the output:-

Usage with @ToString.Exclude

package io.codefountain.project.lombok.tostring;



import lombok.Getter;

import lombok.Setter;

import lombok.ToString;



@ToString

@Getter

@Setter

public abstract class Box {



@ToString.Exclude

private double height;

private double width;

}

Following is the output:-

@EqualsAndHashCode

We can generate equals and hashcode automatically using @EqualsAndHashCode annotation. By default, all non-static and non-transient fields are used in equals and hashcode generation.

SongsAlbum.java

package io.codefountain.project.lombok.equalsandhashcode;



import lombok.EqualsAndHashCode;

import lombok.Getter;

import lombok.Setter;



@EqualsAndHashCode

@Getter

@Setter

public class SongsAlbum {



private static int staticField;

private String albumName;

private int yearOfRelease;

private String[] singer;

private int noOfSongs;

private transient String publisher;

}

SongsAlbumMain.java

package io.codefountain.project.lombok.equalsandhashcode;



public class SongsAlbumMain {

public static void main(String[] args) {

SongsAlbum album1 = new SongsAlbum();

album1.setAlbumName("album1");

album1.setNoOfSongs(10);

album1.setPublisher("publisher1");

album1.setSinger(new String[]{"artist1", "artist2"});

album1.setYearOfRelease(1990);



SongsAlbum album2 = new SongsAlbum();

album2.setAlbumName("album2");

album2.setNoOfSongs(20);

album2.setPublisher("publisher2");

album2.setSinger(new String[]{"artist2", "artist3"});

album2.setYearOfRelease(2001);



SongsAlbum album3 = new SongsAlbum();

album3.setAlbumName("album1");

album3.setNoOfSongs(10);

album3.setPublisher("publisher1");

album3.setSinger(new String[]{"artist1", "artist2"});

album3.setYearOfRelease(1990);



System.out.println("Album1 equal Album 2: "+album1.equals(album2));

System.out.println("Album1 equal Album 3: "+album1.equals(album3));



System.out.println("HashCode Album1: "+album1.hashCode());

System.out.println("HashCode Album2: "+album2.hashCode());

System.out.println("HashCode Album3: "+album3.hashCode());



}

}

Below is the output:-

EqualsAndHashCode output

@NoArgsConstructor, @AllArgsConstructor, and @RequiredArgsConstructor

We can ask Lombok to generate one or more constructors for us. Lombok can generate no-args constructor, all args constructor, and a required args constructor through @NoArgsConstructor, @AllArgsConstructor, and @RequiredArgsConstructor respectively.

In the following Feature class, we have added @NoArgsConstructor and @AllArgsConstructor. We have also added a @ToString annotation to generate toString.

Feature.java

package io.codefountain.project.lombok.constructor;



import lombok.AllArgsConstructor;

import lombok.NoArgsConstructor;

import lombok.ToString;



import java.util.Date;



@NoArgsConstructor

@AllArgsConstructor

@ToString

public class Feature {



private String featureId;

private String featureDescription;

private Date featureImplementationDate;

}

Following is the FeatureMain class to demonstrate the constructor usage:-

FeatureMain.java

package io.codefountain.project.lombok.constructor;



import java.util.Date;



public class FeatureMain {



public static void main(String[] args) {



Feature feature1 = new Feature();

System.out.println("Feature 1:"+feature1.toString());



Feature feature2 = new Feature("F001", "Adding a new theme", new Date());

System.out.println("Feature 2:"+feature2.toString());

}

}

Following is the output of the above class:-

@RequiredArgsConstructor is used to generate a constructor with final fields as well as @NonNull fields.

In the following Recipe class, recipeId and ingredients are mandatory fields. Hence, in the @RequiredArgsConstructor will ensure that a constructor with the recipeId and ingredients is created.

Recipe.java

package io.codefountain.project.lombok.constructor;



import lombok.NonNull;

import lombok.RequiredArgsConstructor;

import lombok.ToString;



@RequiredArgsConstructor

@ToString

public class Recipe {



private final String recipeId;

@NonNull

private String[] ingredients;

private String recipeType;



}

RecipeMain.java

package io.codefountain.project.lombok.constructor;



public class RecipeMain {



public static void main(String[] args) {



Recipe recipe = new Recipe("R001", new String[]{"Vegetable Oil", "Spinach"});

System.out.println(recipe);

}

}

Following is the output:-

@Data

We have seen the usage of @Getter, @Setter, @ToString, @EqualsAndHashCode, and @RequiredArgsConstructor. Most of the times all of these are required in a JavaBean class. Lombok provides a convenient annotation @Data that bundles all of these features together in a single annotation. Thus help us to keep our class definition clean to a certain extent.

All together now: A shortcut for @ToString , @EqualsAndHashCode , @Getter on all fields, @Setter on all non-final fields, and @RequiredArgsConstructor !

User.java

package io.codefountain.project.lombok.data;



import lombok.Data;



import java.util.Date;



@Data

public class User {



private final String userId;

private final char[] password;

private Date lastLogin;



}

UserMain.java

package io.codefountain.project.lombok.data;



import java.util.Date;



public class UserMain {



public static void main(String[] args) {

User john = new User("john", "password".toCharArray());



john.setLastLogin(new Date());



System.out.println(john.toString());

System.out.println(john.getUserId());

}

}

Let us see what .class file is generated for User.java:-

User.class

@Builder

Builder is one of the most popular design patterns and it is often used to construct objects with few mandatory and multiple (optional) fields.

Let us first write a class named Person with few mandatory and some optional fields.

Person.java

package io.codefountain.project.lombok.builder;



import lombok.Builder;

import lombok.NonNull;

import lombok.ToString;



@Builder

@ToString

public class Person {



@NonNull

private String ssnNo;

@NonNull

private String firstName;

@NonNull

private String lastName;



private String[] hobbies;

private String contactNo;

private String emailId;

private String profession;



}

PersonMain.java

In this class, we are using the PersonBuilder static class to build the Person object:-

package io.codefountain.project.lombok.builder;



public class PersonMain {



public static void main(String[] args) {



Person person = Person.builder().ssnNo("S123").firstName("John").lastName("Doe").build();

System.out.println(person);

}

}

@SneakyThrows

@SneakyThrows can be used to sneakily throw checked exceptions without actually declaring this in the method's throws clause. This annotation is applicable to method or constructor.

SneakyThrowsExample.java

In this example, we are throwing a checked exception but we are not declaring in method signature with throws clause.

package io.codefountain.project.lombok.sneakythrows;



import lombok.SneakyThrows;



import java.io.BufferedReader;

import java.io.File;

import java.io.FileReader;

import java.io.IOException;





public class SneakyThrowsExample {



@SneakyThrows(IOException.class)

public String read(){

try{

BufferedReader reader = new BufferedReader(new FileReader((new File("abc.txt"))));

return new String("Mock Data");

}

catch (IOException io){

throw io;

}



}

}

After compilation, this becomes as below:-

SneakyThrowsExample.class

//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//



package io.codefountain.project.lombok.sneakythrows;



import java.io.BufferedReader;

import java.io.File;

import java.io.FileReader;

import java.io.IOException;



public class SneakyThrowsExample {

public SneakyThrowsExample() {

}



public String read() {

try {

try {

new BufferedReader(new FileReader(new File("abc.txt")));

return new String("Mock Data");

} catch (IOException var2) {

throw var2;

}

} catch (IOException var3) {

throw var3;

}

}

}

Note that an additional try-catch block is added to handle the exception.

@Synchronized

@Synchonized is a variant of Java’s synchronized keyword. However, it has some differences. synchronized locks with this lock. Whereas, @Synchonized locks static methods with $LOCK and instance method with $lock objects. With @Synchonized, we can specify a different field name to lock-on.

package io.codefountain.project.lombok.synchronizeddemo;



import lombok.Synchronized;



public class SynchronizedDemo {



private Object myOwnLock = new Object();



@Synchronized

public static void staticMethod(){

System.out.println("This is a static method");

}



@Synchronized

public void instanceMethod(){

System.out.println("This is a instance method");

}



@Synchronized("myOwnLock")

public void methodLockedWithCustomLock(){

System.out.println("This method is locked with custom lock");

}

}

After compilation, this class is changed to as below:-

//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//



package io.codefountain.project.lombok.synchronizeddemo;



public class SynchronizedDemo {

private static final Object $LOCK = new Object[0];

private final Object $lock = new Object[0];

private Object myOwnLock = new Object();



public SynchronizedDemo() {

}



public static void staticMethod() {

synchronized($LOCK) {

System.out.println("This is a static method");

}

}



public void instanceMethod() {

synchronized(this.$lock) {

System.out.println("This is a instance method");

}

}



public void methodLockedWithCustomLock() {

synchronized(this.myOwnLock) {

System.out.println("This method is locked with custom lock");

}

}

}

@Getter(lazy=true)

With the above annotation, we can instruct Lombok to calculate the value of a getter method once and then cache it for subsequent usage. Consider the following example:-

LazyGetter.java

package io.codefountain.project.lombok.accessors;



import lombok.Getter;



public class LazyGetter {



@Getter(lazy = true)

private final double[] cachedData = loadData();



public double[] loadData(){



double[] dataSet = new double[100000000];

for(int i=1; i<dataSet.length; i++){

dataSet[i] += Math.random();

}

return dataSet;

}

}

After compilation, this class is changed to the following:-

LazyGetter.class

//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//



package io.codefountain.project.lombok.accessors;



import java.util.concurrent.atomic.AtomicReference;



public class LazyGetter {

private final AtomicReference<Object> cachedData = new AtomicReference();



public LazyGetter() {

}



public double[] loadData() {

double[] dataSet = new double[100000000];



for(int i = 1; i < dataSet.length; ++i) {

dataSet[i] += Math.random();

}



return dataSet;

}



public double[] getCachedData() {

Object value = this.cachedData.get();

if (value == null) {

synchronized(this.cachedData) {

value = this.cachedData.get();

if (value == null) {

double[] actualValue = this.loadData();

value = actualValue == null ? this.cachedData : actualValue;

this.cachedData.set(value);

}

}

}



return (double[])((double[])(value == this.cachedData ? null : value));

}

}

@Log

This annotation provides logging support. Annotating a class with @Log creates a log field. There are many variations of this annotation to provide support for most of the popular logging frameworks. Note that if the class already has a log field, then Lombok will not generate a log for you and instead write a warning message.

Loggers.java

package io.codefountain.project.lombok.loggers;



import lombok.extern.java.Log;

import lombok.extern.log4j.Log4j;

import lombok.extern.slf4j.Slf4j;



@Log

public class Loggers {



public void logIt(){

log.info("Custom log");

}

}

Note that log is provided by Lombok as we have added @Log annotation in Loggers class definition.

There are few of the variants of @Log annotation:-

@CommonsLog, @Flogger, @JBossLog, @Log4j, @Log4j2, @Slf4j, @XSlf4j

Example of @Slf4j logger:-

package io.codefountain.project.lombok.loggers;



import lombok.extern.slf4j.Slf4j;



@Slf4j

public class Slf4jLogger {



public void logIt(){

log.error("Error occurred.");

}

}

Note that we need to add the corresponding log types maven dependency for Lombok to initialize the log variable.

Should I Use Lombok?

We have seen the usage of various offerings of Lombok. However, there are differences of opinions on whether to use Lombok. Following articles are some references with different views:-

Conclusion

Lombok is one of the handy Java libraries which help developers to focus on actual business code instead of writing boilerplate codes. In this article, I have covered the most common Lombok features useful for application developers on a daily basis. Lombok also provides features that are not covered in this article and can be found here.

All source code can be found on Github.