I couldn’t imagine myself programming Java classes without using the Lombok @Data annotation.

So, in this article, I will try to highlight the importance of the Lombok project and cover in-depth every point related to @Data.

Stay tuned,

@Data Annotation Overview

Using Lombok @Data can surely provide significant technical benefits to your Java project by reducing the required extra code that usually brings no real value to the business side of your application.

What is Lombok Data Annotation?

In short, @Data is a special annotation provided by the lombok project in order to generate automatically all the extra code that is frequently associated with POJOs and Java beans.

You may wonder what is boilerplate code. Well, it is the repetitive code that we have to write each time like:

  • Getter methods for all fields

  • Setter methods for all fields

  • toString() method

  • equals() and hashCode() implementations

  • Constructor that initializes all final fields

The Lombok project provides many annotations. However @Data is very special because it provides an implicit shortcut that binds together all the following annotations:

Bear in mind that each Lombok annotation has one specific purpose, you should use it only when it makes sense.

For example, we can use the @Builder annotation only when we want to implement the builder pattern.

How Lombok @Data Works?

Lombok hooks itself into the compilation phase and acts as an annotation processor that “auto-generate” the extra code you need for your classes.

Its real job is not to generate new files but to modify the existing ones.

By annotating your class with @Data, Lombok will generate:

  • Fully parameterized constructor

  • Setters and getters

  • equals, hashCode and toString methods

Silently during compile time. Awesome, right?

To work with the Lombok project, we need to install it first. Follow the below steps to install it in eclipse (Windows OS):

  • Downloaded the jar file from https://projectlombok.org/download

  • Go to the jar file location, then execute it or run this command line in your terminal: java -jar lombok.jar

  • Once the Lombok window is opened, specify the eclipse.exe location under the Eclipse installation folder

  • Click on Install/Update button to start the installation process

Install Lombok Project

If you are a big fan of Maven like me, you need just to add the following dependency the pom.xml of your project. Feel free to check our article on how to install maven on windows 10.

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

Example Without @Data Annotation

Let’s see how to create a POJO class in Java without using any lombok annotations. For instance, consider the Person class:

    
        package com.azhwani;
        import java.io.Serializable;
        public class Person implements Serializable {
            /**
            * 
            */
            private static final long serialVersionUID = 1L;
            // Properties
            private Long id;
            private String firstName;
            private String lastName;
            private String email;
            private int age;
            // Constructors
            public Person() { 
            }
            public Person(Long id, String firstName, String lastName, String email, int age) { 
                this.id = id;
                this.firstName = firstName;
                this.lastName = lastName;
                this.email = email;
                this.age = age;
            }
            // Getters + Setters
            public Long getId() {
                return id;
            }
            public void setId(Long id) {
                this.id = id;
            }
            public String getFirstName() {
                return firstName;
            }
            public void setFirstName(String firstName) {
                this.firstName = firstName;
            }
            public String getLastName() {
                return lastName;
            }
            public void setLastName(String lastName) {
                this.lastName = lastName;
            }
            public String getEmail() {
                return email;
            }
            public void setEmail(String email) {
                this.email = email;
            }
            public int getAge() {
                return age;
            }
            public void setAge(int age) {
                this.age = age;
            }
            // hashCode + equals + toString methods
            @Override
            public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + age;
                result = prime * result + ((email == null) ? 0 : email.hashCode());
                result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
                result = prime * result + ((id == null) ? 0 : id.hashCode());
                result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
                return result;
            }
            @Override
            public boolean equals(Object obj) {
                if (this == obj)
                    return true;
                if (obj == null)
                    return false;
                if (getClass() != obj.getClass())
                    return false;
                Person other = (Person) obj;
                if (age != other.age)
                    return false;
                if (email == null) {
                    if (other.email != null)
                        return false;
                } else if (!email.equals(other.email))
                    return false;
                if (firstName == null) {
                    if (other.firstName != null)
                        return false;
                } else if (!firstName.equals(other.firstName))
                    return false;
                if (id == null) {
                    if (other.id != null)
                        return false;
                } else if (!id.equals(other.id))
                    return false;
                if (lastName == null) {
                    if (other.lastName != null)
                        return false;
                } else if (!lastName.equals(other.lastName))
                    return false;
                return true;
            }
            @Override
            public String toString() {
                return "Person [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email
                        + ", age=" + age + "]";
            }
        }
    

Oops! Already more than 100 lines of code for a class with 5 properties.

Person class has just 5 properties, so it is normally a basic simple Java class.

However, as you can see, with the extra code of the getters and setters … we ended up with a quite overwhelming boilerplate zero-value code.

You may say that most Java IDE can also auto-generate getters and setters (and even more) for us with just a few clicks.

Yes, you are right. However, the generated code needs to live in your sources and must be maintainable and maintained.

Example with Lombok @Data

With this annotation, we will be able to focus only on what is important in our class: its data and its representation.

The rest will be generated by Lombok. Thanks to the Data annotation.

Now, let’s take a close look at our class Person decorated with @Data:

    
        package com.azhwani;
        import lombok.Data;
        @Data
        public class Person  {
            private Long id;
            private String firstName;
            private String lastName;
            private String email;
            private int age;

        }
    

And that’s how we can go from more than 100 lines of code to just 9 lines. Cool, right?

The code is more clearer and concise that way. As we can see, there is less code to write and maintain.

Lombok does not stop here, it can provide more flexibility and readability to our code.

Lombok @Data example

Please keep in mind that @Data annotation is like having the following implicit annotations:

    
        package com.azhwani;
        import lombok.EqualsAndHashCode;
        import lombok.Getter;
        import lombok.RequiredArgsConstructor;
        import lombok.Setter;
        import lombok.ToString;
        
        @Getter
        @Setter
        @ToString
        @EqualsAndHashCode
        @RequiredArgsConstructor
        public class Person  {
            private Long id;
            private String firstName;
            private String lastName;
            private String email;
            private int age;

        }
    

Demo

    
        package com.azhwani;
        public class LombokDataTest {
            public static void main(String[] args) {
                // TODO Auto-generated method stub
                Person pers = new Person();

                pers.setId(1L);
                pers.setFirstName("Jean");
                pers.setLastName("Deep");
                pers.setEmail("jean.deep@hotmail.com");
                pers.setAge(25);
           
                System.out.println("pers Hash Code: "+ pers.hashCode());
                System.out.println("Person Id : "+pers.getId());
                System.out.println("Person First Name : "+pers.getFirstName());
                System.out.println("Person Email : "+pers.getEmail());
                
                System.out.println(pers.toString());
                
                Person pers2 = new Person();
                pers.setId(1L);
                pers.setFirstName("Samantha");
                pers.setLastName("Collins");
                pers.setEmail("samantha.bella@gmail.com");
                pers.setAge(30);
                
                System.out.println("pers2 Hash Code: "+ pers2.hashCode());

                System.out.println("Equals method : "+ pers.equals(pers2));    
            } 
        }
        
        # Output : 
        pers Hash Code: -1414117140
        Person Id : 1
        Person First Name : Jean
        Person Email : jean.deep@hotmail.com
        Person(id=1, firstName=Jean, lastName=Deep, email=jean.deep@hotmail.com, age=25)
        pers2 Hash Code: 1244954339
        Equals method : false
    

Static Factory Method Using @Data Annotation

With some adjustments added to our previous example, we can end up with a class that can only be instantiated by means of a static factory method.

With the help of the staticConstructor attribute, we can tell lombok to generate a private constructor and a public static factory method:

    
        package com.azhwani;
        import lombok.Data;
        @Data(staticConstructor = "of")
        public class Person  {
            private Long id;
            private String firstName;
            private String lastName;
            private String email;
            private int age;

        }
    

The name of the static method is “of”. Here the “of” convention is considered a best practice because it is heavily used in the new Java 8 APIs.

However, we can also use the old conventions such as newInstance.

That way, we can create our instances using Person.of() instead of new Person().

Demo

    
        package com.azhwani;
        public class LombokDataStaticFactoryTest {
            public static void main(String[] args) {
                Person ovlperson = Person.of();
                ovlperson.setId(1L);
                ovlperson.setFirstName("Olivia");
                ovlperson.setLastName("Wright");
                ovlperson.setEmail("olivia.queen@yahoo.com");
                ovlperson.setAge(30);
                
                System.out.println(ovlperson.toString());
                
                System.out.println("ovlperson Hash Code: "+ ovlperson.hashCode());
                System.out.println("Person Id : "+ovlperson.getId());
                System.out.println("Person First Name : "+ovlperson.getFirstName());
                System.out.println("Person Last Name : "+ovlperson.getLastName());
                System.out.println("Person Age : "+ovlperson.getAge());
            } 
        }
        
        # Output : 
        Person(id=1, firstName=Olivia, lastName=Wright, email=olivia.queen@yahoo.com, age=30)
        ovlperson Hash Code: 357966216
        Person Id : 1
        Person First Name : Olivia
        Person Last Name : Wright
        Person Age : 30
    

Lombok @Data With @Builder

As we have already mentioned in the beginning, @Data generates a required-args constructor.

However, this behavior will change if we add @Builder to the equation. Using @Data and @Builder annotations together will generate an all-args constructor instead.

The problem here is that most DI Frameworks such as Spring rely on a no-args constructor to initialize objects.

So, a nice workaround to fix this problem would be using the @NoArgsConstructor annotation to generate the default constructor for us:

    

        @AllArgsConstructor
        @NoArgsConstructor
        @Value
        @Builder
        public class Person  {
            ...
        }
    

Lombok will complain if we use only @NoArgsConstructor with @Data and @Builder combined, that’s is why we added @AllArgsConstructor too.

@Value vs @Data

The big difference between @Value and Lombok @Data annotation is that @Value is mainly used to create immutable objects.

@Value is a also an all-in-one annotation that combines: @Getter, @AllArgsConstructor, @ToString and @EqualsAndHashCode and @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) annotations.

As we can see @Value does not contain @Setter annotation. @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) allows to mark the fields as private and final.

    
        package com.azhwani;
        import lombok.Value;
        
        @Value
        public class Person  {
            private String name;
            private int section;
        }
    

The Expanded version of the @Value annotation looks like bellow:

    
        package com.azhwani;
        import lombok.Getter;
        import lombok.ToString;
        import lombok.EqualsAndHashCode;
        import lombok.experimental.FieldDefaults;;
        import lombok.AllArgsConstructor;
        
        @Getter
        @ToString
        @EqualsAndHashCode
        @FieldDefaults(makeFinal = true)
        @AllArgsConstructor
        public class Student  {
            private String name;
            private int section;
        }
    

If we de-lombok the value annotation, we will get:

    
        
        public final class Student {
            private final String name;
            private final int section;
            
            public Student(String name, int section) {
                this.name = name;
                this.section = section;
            }
            public String getName() {
             return this.name;
            }
            public int getSection() {
                return this.section;
            }
            @Override
            public boolean equals(final Object obj) {
                if (this == obj)
                    return true;
                if (obj == null)
                    return false;
                if (getClass() != obj.getClass())
                    return false;
                Student other = (Student) obj;
                if (name == null) {
                    if (other.name != null)
                        return false;
                } else if (!name.equals(other.name))
                    return false;
                if (section != other.section)
                    return false;
                return true;
            }
            @Override
            public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + ((name == null) ? 0 : name.hashCode());
                result = prime * result + section;
                return result;
            }
            @Override
            public String toString() {
                return "Student [name=" + name + ", section=" + section + "]";
            }
        }
    

Conclusion

That’s all. In this article, we explained in detail what is Lombok @Data and how we can use it in our Java projects.

I hope you have found this article helpful, enlightening and inspiring to convince you to give Lombok a chance to get into your Java development toolset.

Thanks for reading. See you soon