Getting into Lombok!

Ronith
6 min readJan 17, 2023

--

Table Of Contents

1. @Getter
2. @Setter
3. Constructor Annotations
4. @Data
5. @Builder
6. Something New!

Given the quantum of other Lombok articles, this article will assume that you already know how to integrate the Lombok library into your project and know the basic Lombok annotations.

This article will primarily focus on the nuances of Lombok annotations and the usual pitfalls.

We will be using the User.java file for the examples in this article.

Lombok version used for examples: 1.18.24

public class User {
private long id;
private String firstName;
private String lastName;
private List<Address> addresses;
private Boolean isUserActive;
private Boolean premiumUser;
private boolean fraudUser;
private boolean isEnabled;
}

Note: Keep an eye out for the getter & setter names of isUserActive, premiumUser, fraudUser, and isEnabled fields.

@Getter

Getter annotation will generate the getter boiler code for all fields in the class file.

    public long getId() {
return this.id;
}

public String getFirstName() {
return this.firstName;
}

public String getLastName() {
return this.lastName;
}

public List<Address> getAddresses() {
return this.addresses;
}

public Boolean getIsUserActive() {
return this.isUserActive;
}

public Boolean getPremiumUser() {
return this.premiumUser;
}

public boolean isFraudUser() {
return this.fraudUser;
}

public boolean isEnabled() {
return this.isEnabled;
}

This is the code generated by Lombok for the User class. The interesting thing here is the names of the B(b)oolean getters declared in the class.

If the field type is Boolean (non-primitive data type), the keyword get is prefixed to the field name. Even if the field name has is prefix.

If the field type is boolean (primitive data type), the keyword is is prefixed to the field name if the field name does not already have the is prefix.

For names of other data types, the get keyword is prefixed and the name is camel-cased.

@Setter

Setter annotation will generate the setter boiler code for all fields in the class file.

public void setId(long id) {
this.id = id;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public void setAddresses(List<Address> addresses) {
this.addresses = addresses;
}

public void setIsUserActive(Boolean isUserActive) {
this.isUserActive = isUserActive;
}

public void setPremiumUser(Boolean premiumUser) {
this.premiumUser = premiumUser;
}

public void setFraudUser(boolean fraudUser) {
this.fraudUser = fraudUser;
}

public void setEnabled(boolean isEnabled) {
this.isEnabled = isEnabled;
}

This is the code generated by Lombok for the User class. The interesting thing here is the names of the B(b)oolean setters declared in the class.

If the field type is Boolean (non-primitive data type), the keyword set is prefixed to the field name. Even if the field name has is prefix.

If the field type is boolean (primitive data type), the setter name is derived by removing is prefix (if any) and the keyword set is prefixed to the name.

Both enabled, isEnabled boolean fields will have setEnabled as the setter name.

For names of other data types, the set keyword is prefixed and the name is camel-cased.

Constructor Annotations

There are three constructor annotations in Lombok — @AllArgsConstructor, @NoArgsConstructor, @RequiredArgsConstructor.

Their names accurately justify their functionalities.

@AllArgsConstructor generates a constructor with one argument per field in the class.

public User(long id, String firstName, String lastName, List<Address> addresses, Boolean isUserActive, Boolean premiumUser, boolean fraudUser, boolean isEnabled) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.addresses = addresses;
this.isUserActive = isUserActive;
this.premiumUser = premiumUser;
this.fraudUser = fraudUser;
this.isEnabled = isEnabled;
}

@NoArgsConstructor generates a default constructor with no arguments. Using this annotation in the presence of final fields will result in compile time error.

public User() {
}

@RequiredArgsConstructor will create a constructor with final fields as the only arguments. Using this on our User class will result in the same constructor as @NoArgsConstructor.

public User() {
}

For the sake of explaining, let’s introduce a final field accessLevel to the User class. This will change the generated constructor to:

public User(String accessLevel) {
this.accessLevel = accessLevel;
}

@Data

Consider the below declaration of the User class.

@Setter
@Getter
@RequiredArgsConstructor
@ToString
@EqualsAndHashCode
public class User {
private long id;
private String firstName;
private String lastName;
private List<Address> addresses;
private Boolean isUserActive;
private Boolean premiumUser;
private boolean fraudUser;
private boolean isEnabled;
}

We can condense the five Lombok annotations used to one — @Data.

@Data
public class User {
private long id;
private String firstName;
private String lastName;
private List<Address> addresses;
private Boolean isUserActive;
private Boolean premiumUser;
private boolean fraudUser;
private boolean isEnabled;
}

@Data generates getters for all fields, a useful toString method, and hashCode and equals implementations that check all non-transient fields. It will also generate setters for all non-final fields, as well as a constructor.

@Builder

This annotation removes the necessity of building a UserBuilder class to implement the Builder Pattern. Lombok will generate the boiler code for us. This is the Builder class code generated by Lombok for User.java.

public static class UserBuilder {
private long id;
private String firstName;
private String lastName;
private List<Address> addresses;
private Boolean isUserActive;
private Boolean premiumUser;
private boolean fraudUser;
private boolean isEnabled;

UserBuilder() {
}

public UserBuilder id(long id) {
this.id = id;
return this;
}

public UserBuilder firstName(String firstName) {
this.firstName = firstName;
return this;
}

public UserBuilder lastName(String lastName) {
this.lastName = lastName;
return this;
}

public UserBuilder addresses(List<Address> addresses) {
this.addresses = addresses;
return this;
}

public UserBuilder isUserActive(Boolean isUserActive) {
this.isUserActive = isUserActive;
return this;
}

public UserBuilder premiumUser(Boolean premiumUser) {
this.premiumUser = premiumUser;
return this;
}

public UserBuilder fraudUser(boolean fraudUser) {
this.fraudUser = fraudUser;
return this;
}

public UserBuilder isEnabled(boolean isEnabled) {
this.isEnabled = isEnabled;
return this;
}

public User build() {
return new User(this.id, this.firstName, this.lastName, this.addresses, this.isUserActive, this.premiumUser, this.fraudUser, this.isEnabled);
}

public String toString() {
return "User.UserBuilder(id=" + this.id + ", firstName=" + this.firstName + ", lastName=" + this.lastName + ", addresses=" + this.addresses + ", isUserActive=" + this.isUserActive + ", premiumUser=" + this.premiumUser + ", fraudUser=" + this.fraudUser + ", isEnabled=" + this.isEnabled + ")";
}
}

Note: @Builder annotation also generates a package-private AllArgsConstructor to be used by the build() method.

Something New!

Let’s run a small test case with the User class and the annotations declared here.

I’ll take the below JSON and try to deserialise it.

{
"id": 5478,
"firstName": "Hello",
"lastName": "World",
"addresses": null,
"isUserActive": true,
"premiumUser": true,
"fraudUser": false,
"enabled": true
}

I’ll be using @Data & @Builder annotations on the User class.

@Data
@Builder
public class User {
private long id;
private String firstName;
private String lastName;
private List<Address> addresses;
private Boolean isUserActive;
private Boolean premiumUser;
private boolean fraudUser;
private boolean isEnabled;


public static void main(String[] args) throws JsonProcessingException {

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

String userJson = "{\"id\":5478,\"firstName\":\"Hello\",\"lastName\":\"World\",\"addresses\":null,\"isUserActive\":true,\"premiumUser\":true,\"fraudUser\":false,\"enabled\":true}";
System.out.println(objectMapper.readValue(userJson, User.class));
}
}

Executing this class gives the below output:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.User` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"id":5478,"firstName":"Hello","lastName":"World","addresses":null,"isUserActive":true,"premiumUser":true,"fraudUser":false,"isEnabled":true}"; line: 1, column: 2]

This is because of the clash in the functionalities of @Builder annotation and JSON deserialiser.

@Builder annotation overrides the @RequiredArgsConstructor of @Data annotation and generates only the AllArgsConstructor it needs.

But the JSON deserialiser needs a NoArgsConstructor to initialise the Object. As there is no constructor available, it results in a runtime exception.

To enable the usage of Data & Builder annotations, we can explicitly declare @AllArgsConstructor and @NoArgsConstructor annotations on the User class.

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
private long id;
private String firstName;
private String lastName;
private List<Address> addresses;
private Boolean isUserActive;
private Boolean premiumUser;
private boolean fraudUser;
private boolean isEnabled;


public static void main(String[] args) throws JsonProcessingException {

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

String userJson = "{\"id\":5478,\"firstName\":\"Hello\",\"lastName\":\"World\",\"addresses\":null,\"isUserActive\":true,\"premiumUser\":true,\"fraudUser\":false,\"enabled\":true}";
System.out.println(objectMapper.readValue(userJson, User.class));
}
}

Executing the class post this addition will give the below success result:

User(id=5478, firstName=Hello, lastName=World, addresses=null, isUserActive=true, premiumUser=true, fraudUser=false, isEnabled=true)

Thanks for reading! Have fun learning.

--

--