In this article, I want to present a very powerful tool called Project Lombok. It acts as an annotation processor that allows us to modify the classes at compile time. Project Lombok enables us to reduce the amount of boilerplate code that needs to be written. The main idea is to give the users an option to put annotation processors into the build classpath.
Add Project Lombok to the project
- using gradle
compileOnly "org.projectlombok:lombok:1.16.16"
- using maven
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
<scope>provided</scope>
</dependency>
Project Lombok
Project Lombok provides the following annotations:
- @Getter and @Setter: create getters and setters for your fields
- @EqualsAndHashCode: implements equals() and hashCode()
- @ToString: implements toString()
- @Data: uses the four previous features
- @Cleanup: closes your stream
- @Synchronized: synchronize on objects
- @SneakyThrows: throws exceptions
and many more. Check the full list of available annotations: https://projectlombok.org/features/all
Common object methods
In this example, we have a class that represents User and holds five attributes, for which we want to have an additional constructor for all attributes, toString representation, getters, and setters and overridden equals and hashCode in terms of the email attribute:
private String email;
private String firstName;
private String lastName;
private String password;
private int age;
// empty constructor
// constructor for all attributes
// getters and setters
// toString
// equals() and hashCode()
}
With some help from Lombok, the class now looks like this:
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EqualsAndHashCode(of = {"email"})
public class User {
private String email;
private String firstName;
private String lastName;
private String password;
private int age;
}
As you can see, the annotations are replacing the boilerplate code that needs to be written for all the fields, constructor, toString, etc. The annotations do the following:
- using @Getter and @Setter Lombok is instructed to generate getters and setters for all attributes
- using @NoArgsConstructor and @AllArgsConstructors Lombok created the default empty constructor and an additional one for all the attributes
- using @ToString generates toString() method
- using @EqualsAndHashCode we get the pair of equals() and hashCode() methods defined for the email field (Note that more than one field can be specified here)
Customize Lombok Annotations
We can customize the existing example with the following:
- in case we want to restrict the visibility of the default constructor we can use AccessLevel.PACKAGE
- in case we want to be sure that the method fields won’t get null values assigned to them, we can use @NonNull annotation
- in case we want to exclude some property from toString generated code, we can use excludes argument in @ToString annotation
- we can change the access level of the setters from public to protected with AccessLevel.PROTECTED for @Setter annotation
- in case we want to do some kind of checks in case the field gets modified we can implement the setter method by yourself. Lombok will not generate the method because it already exists
Now the example looks like the following:
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter(AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
@AllArgsConstructor
@ToString(exclude = {"age"})
@EqualsAndHashCode(of = {"email"})
public class User {
private @NonNull String email;
private @NonNull String firstName;
private @NonNull String lastName;
private @NonNull String password;
private @NonNull int age;
protected void setEmail(String email) {
// Check for null and valid email code
this.email = email;
}
}
Builder Annotation
Lombok offers another powerful annotation called @Builder. Builder annotation can be placed on a class, or on a constructor, or on a method.
In our example, the User can be created using the following:
User user = User
.builder()
.email("dimitar.gavrilov@north-47.com")
.password("secret".getBytes(StandardCharsets.UTF_8))
.firstName("Dimitar")
.registrationTs(Instant.now())
.build();
Delegation
Looking at our example the code can be further improved. If we want to follow the rule of composition over inheritance, we can create a new class called ContactInformation. The object can be modelled via an interface:
public interface HasContactInformation {
String getEmail();
String getFirstName();
String getLastName();
}
The class can be defined as the following:
@Data
public class ContactInformation implements HasContactInformation {
private String email;
private String firstName;
private String lastName;
}
In the end, our User example will look like the following:
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Delegate;
@Getter
@Setter(AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
@AllArgsConstructor
@ToString(exclude = {"password"})
@EqualsAndHashCode(of = {"contactInformation"})
public class User implements HasContactInformation {
@Getter(AccessLevel.NONE)
@Delegate(types = {HasContactInformation.class})
private final ContactInformation contactInformation = new ContactInformation();
private @NonNull byte[] password;
private @NonNull Instant registrationTs;
private boolean payingCustomer = false;
}
Conclusion
This article covers some basic features and there is a lot more that can be investigated. I hope you have found a motivation to give Lombok a chance in your project if you find it applicable.