As a developer, I couldn’t imagine myself programming Java classes without using 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 annotation.

Stay tuned,

@Data Annotation Overview

Using lombok @Data can surely provide significant technical benefits to our Java projects. It reduces the required extra code that usually brings no real value to the business side of our applications.

So, let’s see this annotation in detail.

What is Lombok Data Annotation?

In short, @Data is a special annotation provided by the lombok project. Its main purpose is to automatically generate 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 such as:

  • 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 comes with a host of ready-to-use annotations. However @Data is very special because it provides an implicit shortcut that binds together all the following annotations:

Please bear in mind that each annotation has one specific purpose, we should use it only when it makes sense.

For example, we use the @ToString annotation only when we want to implement the toString method.

How Lombok @Data Works?

Lombok hooks itself into the compilation phase and acts as an annotation processor that generates the extra code we need for our classes.

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

By annotating a particular class with @Data, we tell lombok to generate:

  • Parameterized constructor for required fields

  • Setters and getters

  • equals, hashCode and toString methods

And all that silently during compile time. Awesome, right?

Now that we know how lombok works, let’s see how to add it in eclipse.

Install Lombok in Eclipse IDE

To work with lombok plugin, we need to configure it first. Follow the below steps to add it in eclipse (Windows OS):

  • Download 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 Plugin in Eclipse

If you are a big fan of Maven like me, you need just to add the following dependency in the pom.xml of your project:

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

Feel free to check our articles on how to install maven:

Example Without @Data

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 + "]";
            }
        }
    

As we can see, we have 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, 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 automatically by lombok. Thanks to the Data annotation.

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

    
        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 shown above, 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:

    
        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

Now, let’s verify if @Data annotation did its job and that everything works as excepeted:

    
        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 through a static factory method.

With the help of the staticConstructor attribute, we can tell @Data 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.

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

Please keep in mind that we can also use the old naming conventions such as newInstance.

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
    

Use @Data with Other Annotations

The best thing about the data annotation is its flexibility as it can be used alongside other annotations.

For example, we can use @Data with @Setter(AccessLevel.NONE) to make our class read-only. We can use it also with @AllArgsConstructor to generate an all-args constructor instead of the default required args constructor.

Let’s see how we can use data annotation with @ToString to tell lombok to skip the field names in the implementation of the toString() method:

    
        @Data
        @ToString(includeFieldNames=false) 
        public class Employee {
            private int id;
            private String name;
            private double salary;
        }
    

includeFieldNames=false simply informs @ToString to omit each field name and display a comma-separated list of all the field values:

    
        public class Employee  {
            private int id;
            private String name;
            private double salary;
            @Override 
            public String toString() {
                return "Employee(" + this.id + ", " + this.name + ", " + this.salary + ")";
            }
        }
    

Exclude Fields with @Data

By default, @Data includes all the fields when generating getters, setters, and other implementations.

Unfortunately, it does not offer direct support for ignoring and excluding some specific fields.

Though all hope is not lost, we can combine @Data with other lombok specific annotations to address our central question:

    
        @Data
        public class User {
          private int  id;
          private String login;
          
          @ToString.Exclude
          private String password;
          
          @EqualsAndHashCode.Exclude
          @ToString.Exclude
          private boolean enabled;
        }
    

As shown above, we can use the Exclude parameter to ignore and skip the annotated fields.

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 issue would be using the @NoArgsConstructor annotation to generate the default constructor for us.

Lombok will complain if we use @NoArgsConstructor alongside with @Data and @Builder, that’s why we need to add @AllArgsConstructor too.

    

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

Difference Between @Value and @Data

The big difference between @Value and @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).

As we can see @Value, unlike the Data annotation, does not contain @Setter annotation. Omitting setters is the first step for promoting immutability.

@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 our class Student, 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 annotation and how we can use it in our Java projects.

Along the way, we have showcased how to use it with @Builder and explained the core difference between it and the @Value annotation.

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.