Enable Javascript

Please enable Javascript to view website properly

Toll Free 1800 889 7020

Looking for an Expert Development Team? Take 2 weeks Free Trial! Try Now

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:

  • Homepage - https://projectlombok.org/
  • Lombok also integrates well with our IDEs like Eclipse, IntelliJ, Netbeans.

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:

  • Class has many attributes. In this case creating an object via default constructor and explicitly setting required attributes means object creation becomes lengthy.
  • Even if we choose parameterized constructor, the number of parameters in method are quite huge. Also this forces us to send NULL for parameters which are not needed.
  • Same are the issues with object is constructed via Factory.

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 -

  • @Log4j - Log4j framework
  • @JBossLog - JBoss logger
  • @CommonsLog - apache commons logger

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

Software Development Team
Need Software Development Team?
captcha
🙌

Thank you!
We will contact soon.

Oops! Something went wrong.

Recent Blogs

Categories

NSS Note
Trusted by Global Clients