Spring’s @FieldDefaults and @Data: Lombok annotations for cleaner code

Introduction

As Java developers, we often find ourselves stuck in boilerplate code. Accessor methods, modifier methods, constructors, equals(), hashCode(), and toString() are essential, but take up a lot of space and distract from the core logic of the application. The Spring framework is widely used for building enterprise applications and is a fan of reducing boilerplate. However, even with Spring, a certain level of problems is inevitable – unless we bring Project Lombok into the mix.

The annotations provided by Project Lombok can greatly reduce boilerplate code and improve readability and maintainability. Among these annotations, @FieldDefaults and @Data are commonly used, and this article will delve into these two annotations to show their usefulness in Spring applications.

Lombok Introduction

Project Lombok is a library that has made a significant impact on the Java ecosystem by reducing the repetitive and mundane code that developers often need to write. While Java is a powerful and versatile language, it is often criticized for its verbosity, especially when compared to more modern languages like Kotlin or Python. Developers often have to write a lot of “ceremonial” code just to get simple things to work properly, such as creating a plain old Java object (POJO) and its associated getter, setter, equals(), and hashCode() methods toString() .

What problem does Lombok aim to solve

Java’s verbosity is not just a cosmetic issue; It can directly impact the productivity of your development team. Writing boilerplate code is time-consuming and increases the likelihood of errors. Imagine a class with many fields, each requiring its own getter and setter methods. This situation can quickly turn into a maintenance nightmare, especially when you start adding methods like equals() and hashCode() which are supposed to be consistent with each other and update every time a class field changes.

This is where Lombok comes into play. By providing a set of annotations, Lombok automatically generates code at compile time, avoiding verbosity and potential human error. The end result is a more readable and maintainable code base that is easier to understand, debug, and extend.

Lombok How it works

Lombok works by leveraging annotation processing facilities provided in the Java compiler. When compiling code, Lombok scans its comments. Once it finds them, it generates the corresponding code and merges it into your .class file. Essentially, the Java compiler sees a version of your class that looks like you wrote all the boilerplate code by hand, even though you didn’t actually write it.

This method of operation has both advantages and disadvantages. On the plus side, you don’t have to worry about the runtime overhead associated with reflection-based approaches. The downside is that the generated code is not visible in the source file, which can be confusing to those unfamiliar with Lombok.

Set up Lombok in your project

Integrating Lombok into a Spring project is a simple process. If you use Maven, you can add the following dependencies to your pom.xml:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version> <!-- Use the latest version -->
</dependency>
Alternatively, if you use Gradle, include this in your build.gradle:
dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.22' // Use the latest version
    annotationProcessor 'org.projectlombok:lombok:1.18.22'
}

You will also need to install the Lombok plugin in your IDE to ensure it recognizes Lombok annotations and generates appropriate code at compile time.

Popular Lombok Comments

Lombok provides a variety of annotations designed to serve different purposes. Here are some popular ones:

  1. @Getterand @Setter: Automatically generate the getter and setter methods of the field.

  2. @ToString: Generates a human-readable toString() method.

  3. @EqualsAndHashCode: Generate equals() and methods based on fields in the class. hashCode()

  4. @AllArgsConstructor, @NoArgsConstructor, @RequiredArgsConstructor: Generate constructors with different configurations.

However, the focus of this article will be on the @FieldDefaults and @Data annotations, which are very useful in the context of Spring applications.

Annotation@Data

Project Lombok’s @Data annotation is essentially a Swiss Army Knife of Java classes. Using a single annotation, you can automatically generate various methods that you would otherwise have to create manually, which are often simple but can quickly clutter your code base. These include getter and setter methods, equals(), hashCode() and toString() methods. While some may argue that a “one size fits all” approach is not suitable for all situations, there is no denying that @Data can be very useful in many situations, especially for POJOs (plain old Java objects that act as data carriers in an application ).

Anatomy@Data

When you annotate a class with @Data, Lombok generates:

  1. Getter methods for all non-static fields

  2. Setter methods for all non-final, non-static fields

  3. equals() method to check field-by-field equality

  4. A hashCode() method to calculate hash codes based on fields

  5. toString() method returns the string representation of an object

This annotation is meta-annotated with other Lombok annotations such as @Getter, @Setter, @ToString, and @EqualsAndHashCode. Therefore, using @Data is equivalent to using all these annotations at the same time.

This is a simple example to illustrate its @Data role.

No Lombok:

public class Book {
    private String title;
    private String author;
    private int pages;

    public Book() {
    }

    public Book(String title, String author, int pages) {
        this.title = title;
        this.author = author;
        this.pages = pages;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public int getPages() {
        return pages;
    }

    public void setPages(int pages) {
        this.pages = pages;
    }

    @Override
    public boolean equals(Object o) {
        //Implementation
    }

    @Override
    public int hashCode() {
        //Implementation
    }

    @Override
    public String toString() {
        //Implementation
    }
}

Lombok’s @Data:

 import lombok.Data;
@Data
public class Book {
    private String title;
    private String author;
    private int pages;
}

Weighings and Considerations

While @Data it is a powerful tool, it is necessary to understand its limitations and consider when to use it or avoid it.

  1. Performance: For large classes or complex hierarchies, the automatically generated equals() method hashCode() may be inefficient. In this case, a custom implementation might be better.

  2. Immutability:@Data generates setters for all non-final fields, making the class mutable. If immutability is required, you may not want to use this annotation or consider using it in conjunction with @FieldDefaults to make the field final.

  3. Overhead: With @Data, it’s easy to forget what’s going on behind the scenes. Developers unfamiliar with Lombok may find it confusing to use classes whose important functionality is hidden behind a single annotation.

Customized behavior

Lombok provides custom @Data. For example, you can exclude certain fields from the generated equals() and methods by marking them with . Likewise, you can customize the representation by using fields you don’t want to include. hashCode()

@EqualsAndHashCode.ExcludetoString()

@ToString.Exclude

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

@Data
public class CustomBook {
    private String title;
    private String author;

    @EqualsAndHashCode.Exclude
    @ToString.Exclude
    private int pages;
}

In this modified example, the pages field is excluded from the equals() and hashCode() methods as well as the toString() method.

All in all, the @Data annotation is a very versatile tool for any Java developer’s toolkit, especially those using the Spring Framework. By eliminating boilerplate code, it allows you to write more concise and readable code, although it does have some trade-offs that you need to consider carefully.

Annotation @FieldDefaults

One of the less discussed but equally powerful Lombok annotations is @FieldDefaults. This annotation provides a way to set default modifiers for fields in a class. You can control the access level of fields and specify whether they should be final. This is particularly useful in Spring-based applications, where you may want to establish consistent field-level access control without explicitly declaring it for each field.

What @FieldDefaults does

When you annotate a class with @FieldDefaults, you can specify two key elements:

  • Access level: The visibility of the field, such as PRIVATE, PROTECTED, PACKAGE, or PUBLIC. The default value is PRIVATE.

  • Final: A Boolean value specifying whether the field should be final. Default is false.

By using this annotation, you can easily set default behavior for all fields in your class, thus promoting cleaner and more consistent code. When combined with other Lombok annotations such as @Data.

Basic usage

Here’s a simple example showing how to use @FieldDefaults:

import lombok.experimental.FieldDefaults;
import lombok.AccessLevel;

@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class ImmutablePerson {
    String name;
    int age;
}

In this example, we set all fields to private and final. Without @FieldDefaults, you would have to explicitly declare each field as private final.

Combined with other annotations

You often see @FieldDefaults used in Spring applications. @Data While @Data provides rich functionality such as getter, setter and equals() methods hashCode(), @FieldDefaults ensures that all fields are private and possibly immutable. This creates a robust model class that is both concise and safe.

import lombok.Data;
import lombok.experimental.FieldDefaults;
import lombok.AccessLevel;

@Data
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class SecureBook {
    String title;
    String author;
    int pages;
}

In this modified Book class example, not only do we have all the functionality provided by @Data, but we also ensure that all fields are private and final, thus enhancing the immutability and encapsulation of the class.

makeFinal’s comments on properties

The makeFinal=true setting works well when you create immutable classes, but it does limit other Lombok annotations (such as @Setter. In classes annotated with @Data, the makeFinal=true setting actually disables setter methods Generated because final fields cannot be modified once initialized.

@FieldDefaults Therefore, when used in conjunction with @Data, one must consider whether immutability is more important than the ability to change the field value.

The @FieldDefaults annotation provides an elegant way to establish default field-level settings for Java classes. Although comments may seem simple, their impact on code readability and maintainability can be significant. When used wisely, especially in conjunction with other Lombok annotations such as @Data , @FieldDefaults can help you create cleaner, more efficient code with fewer lines and greater consistency.

When to use and when not to use

Deciding when to use Lombok’s @Data and @FieldDefaults annotations is not a black and white decision. While these annotations offer many advantages, such as cleaner and more maintainable code, they also come with their own set of considerations. Below, we’ll detail some scenarios where using these annotations can be helpful, as well as some where you may want to proceed with caution.

When to use @Data and @FieldDefaults

  1. Rapid Prototyping: When you need to quickly develop a prototype or proof of concept, these annotations can significantly speed up your coding.

  2. POJO/Data Class: Using this annotation is a no-brainer when you have a simple class that is mainly used to hold data, and you are sure that you need all the methods generated by @Data.

  3. Spring Boot applications: For Spring Boot applications, which typically value convention over configuration, using @Data and @FieldDefaults is consistent with the framework’s philosophy of reducing boilerplate code.

  4. Short-term projects: In projects with a short life cycle, where maintainability is not a big issue, using these annotations can make the development process more efficient.

  5. Team Consistency: If everyone on the team is familiar with Lombok and its annotations, using @Data and @FieldDefaults can make the code more consistent and easier to understand.

When not to use @Data and @FieldDefaults

  1. Complex business logic: For classes with complex business logic, using @Data can be dangerous because it generates setters that may bypass your logic. In these scenarios, manually writing methods can give you better control.

  2. Immutability: If your application requires immutable objects, be careful with @Data as it generates setters by default. You can still make a field final using @FieldDefaults(makeFinal = true), but this will make the @Data annotated setter generation functionality pointless.

  3. Large teams with varying skill levels: In teams where not everyone is familiar with Lombok, using these annotations can introduce a learning curve and make the codebase harder to understand for newcomers.

  4. Library development: If you are developing a library for public or widespread internal use, using Lombok annotations may force dependencies or unexpected behavior on users.

  5. Performance-critical code: In performance-critical scenarios, automatically generated methods may not be as optimized as manually written methods, especially for operations such as equals() large hashCode() objects.

  6. Inheritance and Polymorphism: When extending a class or implementing it with its own interface and implementation, the hashCode() method of @Data that generates equals() may not adhere to the required convention. In this case, it is recommended to write these methods manually. equals()hashCode()

While @Data and @FieldDefaults can significantly reduce boilerplate and make your code cleaner, their use should be carefully considered. They are excellent tools for simplifying development, but they are not a panacea for every situation. Knowing when to use these annotations (and perhaps more importantly, when not to use them) is crucial to maintaining a balanced, efficient, and understandable codebase.

Conclusion

Lombok Project @FieldDefaults and @Data annotations can significantly reduce boilerplate code in Spring applications, making the code more readable and maintainable. However, one should be cautious about when and where these annotations are used. They are useful for simple data classes, but may not be suitable for classes that contain complex behavior or require the implementation of custom methods such as equals(), hashCode(), or toString().