Deep Dive into Lombok Annotations

I came to know about this powerful Java library “Lombok” last year from a colleague of mine when we were debugging an issue related to automation logs, but didn’t get time since to dig deep into it. While going through a long list of comments in a Twitter thread last month, I came to know how popular, productive and time-saver this library is for the programmers who code in Java. I decided to give it a try and this blog post is a summarization of what I learnt about Lombok and its annotations (along with step-by-step setup of Lombok).

Lombok – in Simple Terms
Lombok is a java library which acts as a pre-processor and eliminates the need for Java coders to write some standard boilerplate code. It uses annotations to specify which boilerplate code do we want Lombok to implement for us. It can get plugged in our IDE of choice, be it Eclipse/ Netbeans/ IntelliJIDEA/ STS/ VSCode, and using annotation processor it generates those code for our programs during compilation. Thereby, making our Java code shorter, cleaner, simpler, more manageable and readable. Java code becomes too verbose sometimes forcing the coders to write a lot of low-value- repetitive, error-prone code which actually perform minimal tasks (boilerplate code). Lombok helps to reduce this verbosity.

Lombok – How it Works
In the backend, it automatically compiles from the annotations used for the boilerplate code and generates the byte code in the “.class” files instead of the boilerplate code actually being written in the source files by the programmer. Amazing, isn’t it? Thanks to the guys who created and contributed to the “ProjectLombok”.

Lombok – Installation and SetUp
To use Lombok in our code, we need to download and add the Lombok jar to our classpath. Or we can add Lombok as dependency to our build tools as well, like Gradle and Maven. I mostly use Gradle, so I add the below dependency to my build.gradle files.

apply plugin: 'war'
repositories {
    mavenCentral()
}
dependencies{
    providedCompile group: 'org.projectlombok', name: 'lombok', version: '1.18.12'
}

For Maven, add the below dependency in the pom.xml file

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

Also, remember to configure Lombok to your IDE (and ask other team members to add to their IDE as well), otherwise it won’t compile. To configure and add it as plugin, click on the lombok.jar file (for gradle and maven you can find it inside the .gradle and .m2 repositories respectively) -> a wizard will open up -> check and select for IDE (if the wizard recognizes your IDE) -> if IDE is not recognized, navigate to the location where the IDE is setup by clicking on “Specify Location…” -> Click Install/Update -> “Install Successful” will be displayed -> Restart Eclipse -> And you are good to go.

Lombok – Accessors (@Getter/@Setter)
First time when I encountered this feature, I instantly remembered a long POJO class which I wrote once for Serialization. At that time, I simply thought that why should someone write this long repetitive, low-value code which contains only the setters and getters for all the fields. Lombok simplifies this by providing “@Getter” and “@Setter” annotations at a class level which, by default, annotates all the non-static fields in that class with the “@Getter” and “@Setter” annotations in the background. “@Getter”/ “@Setter” annotations can be used at individual field levels too. This generates public Accessors (the Getter and the Setter methods) for those fields (you can see those methods in the “Outline” view in case of Eclipse IDE) during the compilation process.

@Getter
@Setter
public class User {
	private String userFirstName;
	private String userLastName;
	private int userAge;
}

We can also specify an access level for each field and hence customize the visibility for the setters and the getters of individual fields of the class.

@Getter
@Setter
public class User {
	private String userFirstName;
	private @Setter(AccessLevel.PROTECTED) String userLastName;
	private @Getter(AccessLevel.PACKAGE) int userAge;
}

Lombok – @NoArgsConstructor/ @AllArgsConstructor/@RequiredArgsConstructor
Like the accessors, Lombok can also automatically create constructors for the classes. The “@NoArgsConstructor” annotation will automatically generate a constructor with no parameters. If the fields are declared final, then this will throw compile error unless “@NoArgsConstructor(force=true)” is used, which will initialize those final fields to 0/false/null.

@NoArgsConstructor(force = true)
@Getter
class User {
	private int age;
	private final String name;
	public User(String name) {
		this.name = name;
	}
}

The “@RequiredArgsConstructor” annotation will generate a constructor with one parameter for each field. All non-initialized final fields, as well as any fields that are marked as “@NonNull” that aren’t initialized where they are declared, will get a parameter. The order of the parameters will match the order in which the fields appear in the class.

@RequiredArgsConstructor
@Getter
class User {
	private final int age;
	private final String name;
}

The “@AllArgsConstructor” annotation will generate a constructor with one parameter for each field in the class irrespective of whether they are final or are having the “@NonNull” annotation

@AllArgsConstructor
@Getter
class User {
	private int age;
	private final String name;
	@NonNull
	private String address;
}

Lombok – Logs
Lombok helps to write log statements for the classes without explicitly declaring the static final log field required by most of the logging frameworks. The logging frameworks supported are CommonsLog, Flogger, JBossLog, Log, Log4j, Log4j2, Slf4j, XSlf4j and can be used even for CustomLogs. For example, using the annotation “@Log4j2” will generate the below LOC for the class “User”:

private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(User.class);

and then you can use your log statements in your class as usual .e.g.
log.info(“This is Info”);
log.trace(“This is Trace”);
log.warn(“This is Warn”);
log.debug(“This is Debug”);
log.error(“This is Error”);
log.fatal(“This is Fatal”);

Lombok – ToString
With the use of its “@ToString” annotation, Lombok can automatically generate an implementation of the toString() method for the objects without the need for you to start the debugger to see the value of the fields. This will help in debuggability. The value that it will print will consist of the class name and all the non-static fields with their values. We can ask Lombok to exclude printing values of any field by using the annotation “@ToString(exclude = { “<variable you want to exclude>” })”

@ToString(exclude = { "address" })
public class User {
	private int age = 25;
	private String name = "Sam";
	private String address = "22B Baker Street";

	public static void main(String[] args) {
		User newUser = new User();
		System.out.println(newUser);
	}
}

Lombok – NullCheck
Lombok provides the “@NonNull” annotation which can provide useful information in case of NullPointerException. Seeing “NullPointerException” without any relevant information is quite annoying – isn’t it?

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

		System.out.println(newUser.getAddressLength(null));
	}

  public int getAddressLength(@NonNull String stringVal){
	return stringVal.length();
}  
}

Running the above code will give us the below output:
Exception in thread “main” java.lang.NullPointerException: stringVal is marked non-null but is null.

Lombok – @Data
Lombok’s “@Data” annotation is an umbrella containing the annotations “@ToString”, “@Getter”, “@Setter” and “@NoArgsConstructor”/ ”@RequiredArgsConstructor”/ “@AllArgsConstructor”. Using the @Data annotation generates all of toString, equals, hashCode and accessor methods (getters and setters) of the fields at the root level. So, basically “@Data” is the only annotation needed for a POJO class along with the fields and nothing else is required. Now, can you imagine how small and readable your POJO class will look like? This will also make the POJO classes concise by hiding the parts of the code which are not important from the program point of view.

@Data
public class User {
	private int age;
	private String name;
	private String address;
}

Lombok – @Synchronized
The “@Synchronized” annotation of Lombok performs similar operation like the “synchronized” keyword in Java by allowing one thread at a time into a particular section of code but it locks on different objects. The “synchronized” keyword locks on “this” but the “@Synchronized” annotation locks on a private field named “$lock”. This can be used in static and instance methods only. If the “$lock” field does not exist, it will get created. If we annotate a static method with “@Synchronized” annotation, then the annotation will lock on a static field named “$LOCK”.

public class User {
	private final Object readLock = new Object();

	@Synchronized
	public static void displayName() {
		System.out.println("Sam");
	}

	@Synchronized
	public int getAge() {
		return 39;
	}

	@Synchronized("readLock")
	public void displayAddress() {
		System.out.println("22B Baker Street");
	}
}

Lombok – @Cleanup
Similar to the “try-with-resources” keyword introduced in Java 7, the “@Cleanup” annotation in Lombok ensures that a given resource is automatically cleaned up before the code execution path exists its current scope. We can do this by annotating any local variable with the “@Cleanup” annotation. Its only difference from try-with-resources is that the latter work only on the objects which implement the “AutoCloseable” interface and has the “close()” method. Be default, the cleanup method is presumed to be close in case of the “@Cleanup” annotation.

public class LombokCleanUpDemo {

	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);
		}
	}
}

Lombok – @Builder
Lombok eases the creation of Builder Design Pattern. The Builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations. Lombok makes it simple to create the builder objects used in the Builder Design Pattern by using the “@Builder” annotation with the class. This “@Builder” annotation produces complex builder APIs for the classes. The “@Builder” annotation can be placed on a class, or on a constructor, or on a method.

@Builder
class Employee {
	private int employeeID;
	private String employeeFirstName;
	private String employeeLastName;
	private String employeeAddress;
	public void displayEmployeeDetails() {
		System.out.println("Employee Detail is as follows: " + "ID: " + employeeID + ", First Name: "
				+ employeeFirstName + ", Last Name: " + employeeLastName + ", Address: " + employeeAddress);
	}
}

public class EmployeeTest {
	public static void main(String[] args) {
		Employee employee = Employee.builder().employeeID(22)			.employeeFirstName("Sam").employeeLastName("Smith").employeeAddress("123B Baker Street").build();
		employee.displayEmployeeDetails();
	}
}

Lombok – @SneakyThrows
Lombok’s “@SneakyThrows” annotation can be used to sneakily throw checked exceptions without actually declaring this in the method’s throws clause.

public class LombokSneakyThrows implements Runnable {
	@SneakyThrows(UnsupportedEncodingException.class)
	public String utf8ToString(byte[] bytes) {
		return new String(bytes, "UTF-8");
	}
	@SneakyThrows
	public void run() {
		throw new Throwable();
	}
}

Conclusion:
Though the new preview language feature “Records” in Java 14 will help to reduce some of the boilerplate code by making it easy, clear and concise to declare classes which are transparent holders for shallowly immutable data and it may feel like it will provide most of the features that Lombok provides in the near future, I am a little optimistic about Lombok because of a couple of reasons:
1) It will take some time for most of the projects to move to Java 14
2) Many more experimental features are being added every now and then in Lombok by a lot of contributors, making it more powerful

Add lombok to your project and check how much code are you able to remove using it.

Credits:
https://projectlombok.org/