How to Explore Project Lombok Using Java

1. Introduction

Java is a very solid, robust, and matured and one of the widely used programming language with a strong community support.


However one of the criticism points it has drawn is its very verbose than compared to its counterparts like Python, Ruby. The best example being Hello world example (In java it’s done in 5-6 lines whereas it’s a simple 1 liner code in python). Similarly reading a file (java takes 10+ lines for it while it’s again a 1 liner job in python).


Lombok-Using-Java

In this article, we’ll explore Project Lombok - a java library which attempts to target this area of Java.


Lombok is a build time dependency, simply write your minified java code using the Lombok’s annotations, while compiling the java file, the annotations are removed and replaced by its desired bytecode. Thus the .class file will be same to one which we would have got after writing java code the normal way.


Following are salient features of Lombok:

Adding Lombok Dependency:

In order to use Lombok in our maven project, simply include its dependency.


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

2. Simplifying POJOS:

A POJO (Plain Old Java Object) is defined as Java class having private attributes, public getters and setters and a default, no-arg constructor. This class is simple as it gets and is not ties to any framework (Eg a class implementing a interface like JMS).


By employing POJOS, code is much simpler. This lends itself to better testing, flexibility, and extensibility. This notion is promoted widely by a lot of frameworks like Spring.


However while writing a simple POJO, we all have gone through the pain of generating the getters/setters, overriding the hashCode and equals method and finally overriding implementation of toString. All modern IDEs provide generators for the same.


2.1 Simplifying getters/setters/toString/equals:

Consider a sample User class with 5 attributes. Also 2 objects of User are equal if they have same id. The code for this simple class is as follows :


publicclassUser { private Integer id; private String firstName; private String lastName; private String email; private String mobile; public Integer getId() { return id; } publicvoidsetId(Integer id) { this.id = id; } public String getFirstName() { returnfirstName; } publicvoidsetFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { returnlastName; } publicvoidsetLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } publicvoidsetEmail(String email) { this.email = email; } public String getMobile() { return mobile; } publicvoidsetMobile(String mobile) { this.mobile = mobile; } @Override publicinthashCode() { finalint prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override publicbooleanequals(Object obj) { if (this == obj) returntrue; if (obj == null) returnfalse; if (getClass() != obj.getClass()) returnfalse; User other = (User) obj; if (id == null) { if (other.id != null) returnfalse; } elseif (!id.equals(other.id)) returnfalse; returntrue; } @Override public String toString() { return"User [id=" + id + ", email=" + email + "]"; } }

The resultant code spans around 80 lines.

Although it’s easily generated by IDE, the issue is with code maintainability. Adding a field invites adding its getters/ setters too.

With lombok the class becomes as simple as:


importlombok.AllArgsConstructor; importlombok.EqualsAndHashCode; importlombok.Getter; importlombok.NoArgsConstructor; importlombok.Setter; importlombok.ToString; @Getter @Setter @NoArgsConstructor @EqualsAndHashCode(of = {"id"}) @ToString(of = {"id", "email"}) publicclassUser { private Integer id; private String firstName; private String lastName; private String email; private String mobile; }

The generated .class file of this User.java and earlier User.java is similar which means Lombok does its job exceptionally. Our User.java class looks more elegant and we no longer have to do the repetitive tasks of creating getters/setters/equals/hashcode and toString methods.


Following is the significance of the Lombok’s annotations used above :


No Annotation Description

1

@Getter

Generates getters methods for all the attributes.
They as generated as per POJO convention and in Camel-case
Eg :
public Integer getId() { return id; }
public String getFirstName() { returnfirstName; }

2

@Setter

Generates setters methods for all the attributes.
They as generated as per POJO convention and in Camel-case

3

@NoArgsConstructor

Creates a no-argument constructor
publicUser() {}

4

@EqualsAndHashCode(of = {"id"})

Generates equals and hashcode implementation such as 2 User objects with same "id" attribute are equal.

5

@ToString(of = {"id", "email"})

Generates toString method returning id and email

public String toString() {
return"User(id=" + getId() + ", email="
+ getEmail() + ")";
}


In addition to above, lombok provides few more annotations like


@AllArgsConstructor - to generate constructor using all the attributes

2.2 Not null checks :


Often it is required that while setting an attribute, we have to perform not null checks. Lombok makes it easy to set these rules :


importlombok.EqualsAndHashCode; importlombok.Getter; importlombok.NoArgsConstructor; importlombok.NonNull; importlombok.Setter; importlombok.ToString; @Getter @Setter @NoArgsConstructor @EqualsAndHashCode(of = {"id"}) @ToString(of = {"id", "email"}) publicclassUser { @NonNull private Integer id; private String firstName; private String lastName; @NonNull private String email; @NonNull private String mobile; }

Here we have explicitly annotated attributes id, email and mobile with @NonNull.

If take on a field, any posted method indicating a value to this field will also generate these null test. Thus here setters of these fields have NULL checks before the value is indicated to them.


The resultant decompiled setters are:


publicvoidsetId(@NonNull Integer id) { if (id == null) thrownewNullPointerException("id"); this.id = id; } publicvoidsetFirstName(String firstName) { this.firstName = firstName; } publicvoidsetLastName(String lastName) { this.lastName = lastName; } publicvoidsetEmail(@NonNull String email) { if (email == null) thrownewNullPointerException("email"); this.email = email; } publicvoidsetMobile(@NonNull String mobile) { if (mobile == null) thrownewNullPointerException("mobile"); this.mobile = mobile; }

Thus null checks are applied on the setters of mobile, id and email while other setters are normal.


2.3 Further reducing Annotations


Lombok provides us further mechanism to reduce the annotations.

A convenience annotation @Data is further provided.


importlombok.Data; @Data publicclassUser { private Integer id; private String firstName; private String lastName; private String email; private String mobile; }

This produces getters for all fields, a useful to String method, and hash Code and equals expectation that check all non-transient fields.

This also generate setters for all non-final fields, as well as a constructor.

@Data annotation is equivalent to {@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode}.



3. Lombok Support for Builder Pattern

Builder design pattern is mostly employed when:

Builder pattern solves the above issues by providing a way to build the object step-by-step and provide a method (build) that will actually return the final Object which will be consistent.


The builder for our above User class is configured as :


publicclassUser { private Integer id; private String firstName; private String lastName; private String email; private String mobile; // getters and setters privateUser(UserBuilder builder) { this.id = builder.id; this.firstName = builder.firstName; this.lastName = builder.lastName; this.email = builder.email; this.mobile = builder.mobile; } publicstaticclassUserBuilder { private Integer id; private String firstName; private String lastName; private String email; private String mobile; publicUserBuilderid(Integer id) { this.id = id; returnthis; } publicUserBuilderfirstName(String fName) { this.firstName = fName; returnthis; } publicUserBuilderlastName(String lName) { this.lastName = lName; returnthis; } publicUserBuilderemail(String email) { this.email = email; returnthis; } publicUserBuildermobile(String mob) { this.mobile = mob; returnthis; } public User build() { returnnew User(this); } } }

The resultant User object can be created using builder as


User u = newUserBuilder().id(1).email("x").build();

Maintaining this builder can be a maintenance hazard, when the fields are added/removed in main User class.


Lombok provides @Builder annotation which does the same thing for us.


importlombok.Builder; importlombok.Data; @Data @Builder publicclassUser { private Integer id; private String firstName; private String lastName; private String email; private String mobile; }

The code to use the builder is :


publicstaticvoidmain(String[] args) { User u = newUserBuilder().id(1).email("x").build(); System.out.println(u); } The resultant output generated is : User(id=1, firstName=null, lastName=null, email=x, mobile=null)

4. Cleanup Resources

In java when we deal with resources like Files, Input/Output streams, then its a bad practice to let the resources open. Ideal way is to cleanup the resources in finally block before exiting the function.


With lombok’s @Cleanup annotation, we no longer have to remember to cleanup the resources.


Consider the following code :


publicstaticvoidmain(String[] args) { Scanner sc = new Scanner(System.in); System.out.println(sc.nextLine()); System.out.println(sc.nextLine()); }

Here compiler generates warning “Resource leak :sc is never closed”.


On annotating the Scanner sc with @Cleanup, we never have to worry about cleaning/closing it.


publicstaticvoidmain(String[] args) { @Cleanup Scanner sc = new Scanner(System.in); System.out.println(sc.nextLine()); System.out.println(sc.nextLine()); } ------------------------------------- The equivalent code is : ------------------------------------- publicstaticvoidmain(String[] args) { Scanner sc = new Scanner(System.in); try { System.out.println(sc.nextLine()); System.out.println(sc.nextLine()); } ---------------------- finally{ if (sc!= null) sc.close(); } }

5. Handling checked Exceptions

We often come across API methods which throw checked exceptions like SQLException, IOException. Here our methods are forced to throw them too. An alternate way is swallowing these checked exceptions, logging them and wrapping them in runtime/unchecked exceptions, so that compiler doesn’t complain.


Lombok provides the @SneakyThrows annotation.


This can be placed on a method to essentially “swallow” the checked exceptions, allowing you to omit the try-catch block completely.


Consider the following code sample


publicstaticvoidmain(String[] args) { InputStream str = newFileInputStream(new File("")); System.out.println(str); }
Here the FileInputStream throws FileNotFoundException which is checked exception and hence compiler will continue to give error until method declares it. This is done as follows :


publicstaticvoidmain(String[] args) throwsFileNotFoundException { InputStream str = newFileInputStream(new File("")); System.out.println(str); }

Further if we don't want to change the method signature, then we need to write the logic to swallow the exception on our own.


With @SneakyThrows, the lombok implements exception swallowing logic.


@SneakyThrows publicstaticvoidmain(String[] args) { InputStream str = newFileInputStream(new File("")); System.out.println(str); }
The above code is equivalent to : publicstaticvoidmain(final String[] args) { try { finalInputStream str = newFileInputStream(new File("")); System.out.println(str); } catch (Throwable ex) { thrownewRuntimeException(ex); } }

6. Logging

In java applications, we employ various logging frameworks like Log4j, Logback for efficient logging. In addition to it, we use Logging facade like Slf4j so that underlying logging implementation can be changed without affecting the code.


For Slf4J the typical way to declare and implement the logging is :


import org.slf4j.Logger; import org.slf4j.LoggerFactory; publicclassUser { privatestaticfinal Logger LOG = LoggerFactory.getLogger(User.class); publicstaticvoidmain(String[] args) { LOG.info("Started main"); LOG.info("Ended main"); } }

Lombok takes care of the logger declaration if you place the @Log annotation.


@Slf4j publicclassUser { publicstaticvoidmain(String[] args) { log.info("Started main"); log.info("Ended main"); } }

Thus we can directly start using logger without defining it.


Lombok provides annotations for different logging frameworks -

7. Conclusion:

Thus we have explored major feature offerings of Project Lombok to reduce verbosity of the Java software development company

A full list of its features can be found at https://projectlombok.org/features/all

  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img