How to use Lombok in Java to reduce Boilerplate Code

Introduction

In this tutorial we will see how to use lombok in Java based projects or applications to reduce boilerplate code. Lombok is a Java library that automatically provides getters/setters or equals method in your Java class. Just by placing an annotation your class has a fully featured builder, it automates your logging variables, and much more. You can also use on your any Java based project including Spring based projects.

In java programming lots of boilerplate code are repeated in many parts of the application with little alteration. Lombok aims to replace such worst offenders with a simple set of annotations. The annotations are not used to generate the code but directly utilized by the application.

Prerequisites

Java 8 or 12, Gradle 5.6, Eclipse 4.12, lombok jar, Windows 10 64 bit

Lombok Installation

You need to first install the lombok into Windows machine.

  • First get the jar file from the link.
  • Put this downloaded jar into any location.
  • Open command prompt and navigate to the jar directory and execute the command java -jar lombok.jar. You can also run the jar file by double-clicking on it.
  • Next a window will appear as shown in the below image. It scans your system and look for the IDE. For me it got Eclipde IDE.
lombok in java
  • Now simply click “Install/Update”.
  • You will see installation successful:
lombok in java
  • You are done with lombok installation. In order to take the changes effect into your IDE you need to restart your Eclipse.

Creating Project

Create a gradle based project in Eclipse. The project name is java-lombok-example.

Build Script

Update the build.gradle script to include the required dependency lombok.

plugins {
    id 'java-library'
}

sourceCompatibility = 12
targetCompatibility = 12

repositories {
	mavenCentral()
    maven {
    	url "http://projectlombok.org/mavenrepo"
    }    
}

dependencies {
	implementation('org.projectlombok:lombok:1.18.10')
}

Examples on Various Annotations

We will see here different operations even without generating the methods which are directly provided by Project Lombok.

For example, a Java project may require hundreds of lines of boilerplate code for defining simple data classes. These classes generally contain a number of fields, getters and setters for those fields, as well as equals() and hashCode() implementations. In simple scenarios, Project Lombok can reduce these classes to the required fields and a single @Data annotation.

@Data

@Data annotation will provide the getters and setters for your class. You need to put this annotation on a class level.

Create a class called User and annotate with @Data:

package com.roytuts.java.lombok.example.model;

import lombok.Data;

@Data
public class User {

	private String name;
	private String email;

}

Now if you see the Outline view in Eclipse then you will see many methods are provided by lombok.

lombok in java

Next we will test the above class by setting the properties using setters and printing the properties thereafter using toString() method.

package com.roytuts.java.lombok.example;

import com.roytuts.java.lombok.example.model.User;

public class JavaLombokTest {

	public static void main(String[] args) {
		User user = new User();

		user.setName("Soumitra");
		user.setEmail("soumitra@email.com");

		System.out.println(user);
	}

}

Running the above main class will give you below output:

User(name=Soumitra, email=soumitra@email.com)

@Getter, @Setter

Using @Data annotation we have seen many methods are provided by lombok. Therefore you may not need all these methods and you need only @Getter and @Setter methods, then you need to use @Getter and @Setter annotations. If you need only getters then use @Getter annotation and if you need only setters then you @Setter annotation.

lombok in java

Now if you run the main class again you will get below output:

com.roytuts.java.lombok.example.model.User@4517d9a3

As there is no toString() method provided by lombok in the above User class, so you will get the above output of object.

You can even specify the access level of @Getter and/or @Setter:

@Setter(AccessLevel.PROTECTED)

You can even specify @Getter and/or @Setter on individual attribute:

@Getter
private String name;

@Getter
@Setter
private String email;

@NonNull

@NonNull annotation is used for fast-fail null check on a particular Java attribute.

When placed on a field or attribute, Lombok will generate a setter method with a null check that will result in a NullPointerException when you try to set null value to the field or attribute. Additionally, if Lombok generates a constructor for the class then the field will be added to the constructor signature and the null check will be included in the generated constructor code.

You can place this annotation or a class level or field level. Class level annotation will set null check for all fields or attributes. Field or attribute level annotation will set null for that particular field.

For example, consider the below example:

lombok in java

Now try to set the value null to email field:

package com.roytuts.java.lombok.example;

import com.roytuts.java.lombok.example.model.User;

public class JavaLombokTest {

	public static void main(String[] args) {
		User user = new User();

		user.setName("Soumitra");
		user.setEmail(null);

		System.out.println(user.getEmail());
	}

}

Running the above main class will throw below exception:

Exception in thread "main" java.lang.NullPointerException: email is marked non-null but is null
	at com.roytuts.java.lombok.example.model.User.setEmail(User.java:8)
	at com.roytuts.java.lombok.example.JavaLombokTest.main(JavaLombokTest.java:12)

@ToString

From the name of the annotation it is obvious that it provides the implementation of toString() method.

By default, any non-static fields will be included in the output of the method in name-value pairs. You may suppress the names of the properties or fields in the output by setting the annotation parameter includeFieldNames to false.

Specific fields can be excluded from the output of the generated method by including their field names in the exclude parameter.

Alternatively, you can use of parameter to list only those fields which are desired in the output. The output of the toString() method of a superclass can also be included by setting the callSuper parameter to true.

lombok in java

Include only fields which you need to include in toString() output. For example, in the below class I have included only name and email.

package com.roytuts.java.lombok.example.model;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString(of = { "name", "email" })
public class User {

	private String name;

	private String email;

	private String address;

}

Test the above example:

package com.roytuts.java.lombok.example;

import com.roytuts.java.lombok.example.model.User;

public class JavaLombokTest {

	public static void main(String[] args) {
		User user = new User();

		user.setName("Soumitra");
		user.setEmail("soumitra@email.com");
		user.setAddress("Earth");

		System.out.println(user);
	}

}

Running the above class will give you below output:

User(name=Soumitra, email=soumitra@email.com)

You can exclude using the exclude parameter:

@ToString(exclude = { "address" })

To exclude all field names use below example:

@ToString(includeFieldNames = false)

For the same User and main class above you will get below output:

User(Soumitra, soumitra@email.com, Earth)

Call super class toString() using below example:

@ToString(callSuper = true)

@EqualsAndHashCode

We know that there are contracts between equals() and hashCode() methods in Java. So this annotation @EqualsAndHashCode is applicable on class level.

By default any field that is non-static or non-transient will be considered for inclusion into equals() and hashCode() methods.

Like @ToString annotation exclude parameter is used to prevent field being included in the generation logic. of parameter is used to include only fields which you want in your generation logic.

There is also a callSuper parameter for this annotation. Setting it to true will cause equals() to verify equality by calling the equals() from the superclass before considering fields in the current class. For the hashCode() method, it results in the incorporation of the results of the superclass’s hashCode() in the calculation of the hash.

Parent Class:

package com.roytuts.java.lombok.example.model;

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

@Getter
@Setter
@ToString
@EqualsAndHashCode(exclude = "address")
public class User {

	private String name;

	private String email;

	private String address;

}

Child Class:

package com.roytuts.java.lombok.example.model;

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

@Getter
@Setter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Employee extends User {

	private int id;

}

Testing:

package com.roytuts.java.lombok.example;

import com.roytuts.java.lombok.example.model.Employee;

public class JavaLombokTest {

	public static void main(String[] args) {
		Employee employee = new Employee();
		employee.setId(1234);

		System.out.println(employee);

		System.out.println(employee.hashCode());

		employee.setName("Soumitra");
		employee.setEmail("soumitra@email.com");
		employee.setAddress("Earth");

		System.out.println(employee);

		System.out.println(employee.hashCode());
	}

}

Running the main class will give you below output:

Employee(super=User(name=null, email=null, address=null), id=1234)
358833
Employee(super=User(name=Soumitra, email=soumitra@email.com, address=Earth), id=1234)
-1413876382

@Cleanup

The @Cleanup annotation is used to release the allocated resources. When a local variable is annotated with @Cleanup, any subsequent code is wrapped in a try/finally block that guarantees that the cleanup method is called at the end of the current scope.

By default @Cleanup assumes that the cleanup method is named “close“, as with input and output streams. However, a different method name can be provided to the annotation’s value parameter.

Only methods which take no parameters are able to use this @Cleanup annotation.

package com.roytuts.java.lombok.example;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import lombok.Cleanup;

public class LombokCleanupExample {

	public static void main(String[] args) {
		cleanUp();
	}

	public static void cleanUp() {
		try {
			@Cleanup
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			baos.write(new byte[] { 'C', 'l', 'e', 'a', 'n', 'e', 'd', ' ', 'U', 'p' });
			System.out.println(baos.toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

@Synchronized

Using the synchronized keyword on a method can result undesirable output on a multi-threaded environment. The synchronized keyword will lock on the current object (this) in the case of an instance method or on the class object for a static method. This means developer does not have option to apply lock on the same object, resulting in a deadlock. It is generally advisable to instead lock explicitly on a separate object that is dedicated solely to that purpose and not exposed in such a way as to allow unsolicited locking. Project Lombok provides the @Synchronized annotation for that very purpose.

Annotating an instance method with @Synchronized will prompt Lombok to generate a private locking field named $lock on which the method will lock prior to executing. Similarly, annotating a static method in the same way will generate a private static object named $LOCK for the static method to use in an identical fashion. A different locking object can be specified by providing a field name to the annotation’s value parameter. When a field name is provided, the developer must define the property as Lombok will not generate it.

package com.roytuts.java.lombok.example;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import lombok.Synchronized;

public class LombokSynchronizedExample {

	private DateFormat format = new SimpleDateFormat("MM-dd-YYYY");

	public static void main(String[] args) {
		LombokSynchronizedExample example = new LombokSynchronizedExample();
		System.out.println(example.synchronizedFormat(new Date()));
	}

	@Synchronized
	public String synchronizedFormat(Date date) {
		return format.format(date);
	}

}

Remember for java 8 onward you don’t need to synchronization for date as it already provides thread safety.

@SneakyThrows

To make error goes away @SneakyThrows annotation is used.

Consider the below example:

lombok in java

From the above output it is obvious that you have to either throw exception or surround with try/catch block. Therefore if you annotate the handleFile() method with @SneakyThrows annotation then error will go away.

The method will look like below:

package com.roytuts.java.lombok.example;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

import lombok.SneakyThrows;

public class LombokSneakyThrowsExample {

	@SneakyThrows
	public void handleFile(String file) {
		InputStream inputStream = new FileInputStream(new File(file));
	}

}

Now you see in the above class we don’t need to throw any exception or surround with try/catch block.

@NoArgsConstructor and @AllArgsConstructor

You may have requirement for adding all arguments and no argument constructors in your Java class. Then you can use these annotations on class level. The @NoArgsConstructor will provide a constructor without any parameter and the @AllArgsConstructor will provide a constructor will all fields as parameters.

You can specify the access level of the constructor using access parameter. By default access level is public.

Thanks for reading.

Leave a Reply

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