1. Overview

In this article, we'll look at the @Data and @Value annotations of Lombok.

@Data and @Value are two shortcut annotations that bring a lot of functionality to our classes. This is because they cover most of the boilerplate code needed for POJOs.

2. @Data for Mutable Classes

@Data is a shortcut annotation that combines @Getter, @Setter, @RequiredArgsConstructor, @ToString, and @EqualsAndHashCode.

Generally, we must use @Data for mutable classes whose internal state can change over time.

In order to see what Lombok adds to our class, we have the following Employee class:

@Data
public class Employee {

    private String name;
    private int salary;
}

When we expand the @Data annotation, Employee becomes:

@Getter
@Setter
@RequiredArgsConstructor
@ToString
@EqualsAndHashCode
public class EmployeeExpanded {

    private String name;
    private int salary;
}

Lastly, when we de-lombok the @Data annotation, we can see the actual generated methods:

public class Employee {

    private String name;
    private int salary;

    public Employee() {
    }

    public String getName() {
        return this.name;
    }

    public int getSalary() {
        return this.salary;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        }
        if (!(o instanceof Employee)) {
            return false;
        }
        final Employee other = (Employee) o;
        if (!other.canEqual((Object) this)) {
            return false;
        }
        final Object this$name = this.getName();
        final Object other$name = other.getName();
        if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
            return false;
        }
        if (this.getSalary() != other.getSalary()) {
            return false;
        }
        return true;
    }

    protected boolean canEqual(final Object other) {
        return other instanceof Employee;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final Object $name = this.getName();
        result = result * PRIME + ($name == null ? 43 : $name.hashCode());
        result = result * PRIME + this.getSalary();
        return result;
    }

    public String toString() {
        return "Employee(name=" + this.getName() + ", salary=" + this.getSalary() + ")";
    }
}

Here, we have several methods that are generated by the Lombok library. Firstly, Lombok generates getName and getSalary because of the implicit @Getter annotation. Similarly, @Setter creates the setter methods and @ToString creates the toString method.

2.1. Create Static Factory Method for @Data

The @Data annotation includes the staticConstructor attribute. When we set this attribute, Lombok generates a private constructor and a public static factory method:

@Data(staticConstructor = "of")
public class Employee {

    private String name;
    private int salary;
}

The name of our static factory method is of  - which is also the recommended name.

private Employee() {
}

public static Employee of() {
    return new Employee();
}

// Other methods...

In the end, we create our instances using Employee.of() instead of new Employee().

2.2. Configure @Data

Lombok provides some configuration keys for the @Data annotation. To apply this configuration keys, we must first have a lombok.config file in our project.

The lombok.data.flagUsage key tells Lombok to log a message when there is a usage of @Data:

# [warning | error] (default: not set)
lombok.data.flagUsage = error

There is no default value for this configuration. Then we're setting it as error. As a result, when Lombok detects usage of @Data, it'll log an error message like "Use of @Data is flagged according to lombok configuration.".

3. @Value for Immutable Classes

@Value is similar to the @Data annotation, but it creates immutable objects.

It is a shortcut annotation which combines @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE), @Getter, @AllArgsConstructor, @ToString and @EqualsAndHashCode. However, it doesn't have @Setter. Moreover, the usage of @FieldDefaults makes every instance field private final.

We have the  following Employee class with the @Value annotation:

@Value
public class Employee {

    private String name;
    private int salary;
}

When we expand the @Value annotation, it becomes:

@Getter
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@AllArgsConstructor
@EqualsAndHashCode
@ToString
public class EmployeeExpanded {

    private String name;
    private int salary;
}

Then when we de-lombok the @Value annotation, we see the generated methods:

public final class Employee {

    private final String name;
    private final int salary;

    public Employee(String name, int salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return this.name;
    }

    public int getSalary() {
        return this.salary;
    }

    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        }
        if (!(o instanceof Employee)) {
            return false;
        }
        final Employee other = (Employee) o;
        final Object this$name = this.getName();
        final Object other$name = other.getName();
        if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
            return false;
        }
        if (this.getSalary() != other.getSalary()) {
            return false;
        }
        return true;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final Object $name = this.getName();
        result = result * PRIME + ($name == null ? 43 : $name.hashCode());
        result = result * PRIME + this.getSalary();
        return result;
    }

    public String toString() {
        return "Employee(name=" + this.getName() + ", salary=" + this.getSalary() + ")";
    }
}

Here, we have several Lombok-generated methods similar to @Data. One important difference is that we don't have any setter methods. Also, all fields are final even though we didn't explicitly mark them as such. Similarly, the class is marked as final.

3.1. Create Static Factory Method for @Value

The @Value annotation includes the attribute staticConstructor. When set, Lombok makes the constructor private and then creates a static factory method for constructing objects:

@Value(staticConstructor = "of")
public class Employee {

    private String name;
    private int salary;
}

We're naming our static factory method as of:

private Employee(String name, int salary) {
    this.name = name;
    this.salary = salary;
}
    
public static Employee of(String name, int salary) {
    return new Employee(name, salary);
}

// other methods...

The construction of Employee changes from new Employee("John", 100) to Employee.of("John", 100).

3.2. Change Access Modifiers

Since we use @Value mainly to create immutable objects, Lombok marks the class as final and the instance variables as private final. However, this isn't a strict rule.

Firstly, we can change the access modifiers of the fields using @NonFinal and @PackagePrivate:

@Value
public class Employee {

    @NonFinal
    @PackagePrivate
    private String name;

    @NonFinal
    @PackagePrivate
    private int salary;
}

In the end, name and salary won't be final or private.

Secondly, we can also remove the final modifier from our class using @NonFinal:

@Value
@NonFinal
public class Employee {

// Fields...
}

So the Employee class will have the signature of public class Employee instead of public final class Employee.

3.3. Configure @Value

Lombok provides configuration keys also for @Value.

The lombok.value.flagUsage configuration key tells Lombok to log a message when @Value is used:

# [warning | error] (default: not set)
# lombok.value.flagUsage = error

Since there is no default value for this configuration, it won't take effect unless configured. Here, we're setting it as error. So if Lombok detects usage of @Value, it logs an error message during compilation stating "Use of @Value is flagged according to lombok configuration.".

4. Summary

In this tutorial, we investigated how we can use the Lombok @Data and @Value annotations.

As always the source code is available on Github.