Guide to Lombok Library in Java

Introduction to Lombok

Lombok is a Java library that reduces boilerplate code by using annotations to generate commonly used methods like getters, setters, constructors, equals, hashCode, and toString. It integrates seamlessly with IDEs and the build tools, making Java code more concise and readable. This guide will cover all the use cases, features, annotations, and complex and nested examples with output, as well as using Lombok in the Spring Boot project.

Installation

Adding Lombok to Your Project

To use Lombok, add the following dependency to your pom.xml if you're using Maven:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version> <!-- or the latest version -->
    <scope>provided</scope>
</dependency>

For Gradle:

compileOnly 'org.projectlombok:lombok:1.18.20'
annotationProcessor 'org.projectlombok:lombok:1.18.20'

Enabling Lombok in IDEs

  • IntelliJ IDEA: Install the Lombok plugin from the JetBrains plugin repository and enable annotation processing in Settings > Build, Execution, Deployment > Compiler > Annotation Processors. Check out this page.
  • Eclipse: Install the Lombok plugin from the Eclipse Marketplace and ensure annotation processing is enabled. Check out this guide to install the Lombok plugin in Eclipse IDE.

Basic Usage

Generating Getters and Setters

Lombok can generate getters and setters for class fields using @Getter and @Setter annotations.

Example

import lombok.Getter;
import lombok.Setter;

public class Person {
    @Getter @Setter
    private String firstName;

    @Getter @Setter
    private String lastName;

    @Getter @Setter
    private int age;
}

public class LombokExample {
    public static void main(String[] args) {
        Person person = new Person();
        person.setFirstName("Amit");
        person.setLastName("Sharma");
        person.setAge(30);

        System.out.println("Person: " + person.getFirstName() + " " + person.getLastName() + ", Age: " + person.getAge());
    }
}

Output

Person: Amit Sharma, Age: 30

Explanation: The @Getter and @Setter annotations automatically generate the getter and setter methods for the fields, reducing boilerplate code.

Generating toString, Equals, and HashCode

Lombok can generate toString, equals, and hashCode methods using @ToString, @EqualsAndHashCode, and @Data annotations.

Example

import lombok.Data;

@Data
public class Person {
    private String firstName;
    private String lastName;
    private int age;
}

public class LombokExample {
    public static void main(String[] args) {
        Person person = new Person();
        person.setFirstName("Vikas");
        person.setLastName("Singh");
        person.setAge(28);

        System.out.println(person);
    }
}

Output

Person(firstName=Vikas, lastName=Singh, age=28)

Explanation: The @Data annotation combines @Getter, @Setter, @ToString, @EqualsAndHashCode, and @RequiredArgsConstructor, providing a comprehensive solution for data classes.

Advanced Features

Generating Constructors

Lombok can generate constructors using @NoArgsConstructor, @RequiredArgsConstructor, and @AllArgsConstructor annotations.

Example

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
public class Employee {
    private String name;
    private final String id;
    private int age;

    // Getters and Setters
}

public class LombokExample {
    public static void main(String[] args) {
        Employee emp1 = new Employee();
        Employee emp2 = new Employee("E123");
        Employee emp3 = new Employee("Rahul", "E124", 35);

        System.out.println(emp1);
        System.out.println(emp2);
        System.out.println(emp3);
    }
}

Output

Employee(name=null, id=null, age=0)
Employee(name=null, id=E123, age=0)
Employee(name=Rahul, id=E124, age=35)

Explanation: The @NoArgsConstructor annotation generates a no-argument constructor, @RequiredArgsConstructor generates a constructor with required (final) fields and @AllArgsConstructor generates a constructor with all fields.

Using Builder Pattern

Lombok can generate builder pattern methods using the @Builder annotation.

Example

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class Student {
    private String name;
    private int age;
    private String rollNumber;
    private String address;
}

public class LombokExample {
    public static void main(String[] args) {
        Student student = Student.builder()
                .name("Priya")
                .age(22)
                .rollNumber("R123")
                .address("Delhi")
                .build();

        System.out.println(student);
    }
}

Output

Student(name=Priya, age=22, rollNumber=R123, address=Delhi)

Explanation: The @Builder annotation generates a builder pattern for the class, allowing for flexible and readable object creation.

Nested and Complex Examples

Nested Classes

Lombok can handle nested classes and apply annotations to inner classes as well.

Example

import lombok.Data;

@Data
public class Company {
    private String name;
    private Address address;

    @Data
    public static class Address {
        private String street;
        private String city;
    }
}

public class LombokExample {
    public static void main(String[] args) {
        Company.Address address = new Company.Address();
        address.setStreet("MG Road");
        address.setCity("Bangalore");

        Company company = new Company();
        company.setName("Tech Solutions");
        company.setAddress(address);

        System.out.println(company);
    }
}

Output

Company(name=Tech Solutions, address=Company.Address(street=MG Road, city=Bangalore))

Explanation: The @Data annotation can be applied to nested classes, generating the necessary methods for inner class objects.

Using @Value for Immutable Objects

Lombok can generate immutable objects using the @Value annotation.

Example

import lombok.Value;

@Value
public class ImmutableEmployee {
    private String name;
    private String id;
    private int age;
}

public class LombokExample {
    public static void main(String[] args) {
        ImmutableEmployee emp = new ImmutableEmployee("Suresh", "E125", 40);

        System.out.println(emp);
    }
}

Output

ImmutableEmployee(name=Suresh, id=E125, age=40)

Explanation: The @Value annotation makes the class immutable by making all fields private and final, generating a constructor for all fields, and not generating setters.

Additional Lombok Annotations

@NonNull

The @NonNull annotation ensures that a method parameter or field cannot be null. It generates a null check and throws a NullPointerException if the value is null.

Example

import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class Customer {
    @NonNull
    private String name;
    private String email;

    public void setEmail(@NonNull String email) {
        this.email = email;
    }
}

public class LombokExample {
    public static void main(String[] args) {
        try {
            Customer customer = new Customer(null);
        } catch (NullPointerException e) {
            System.out.println("Caught NullPointerException: " + e.getMessage());
        }

        Customer customer = new Customer("Ramesh");
        try {
            customer.setEmail(null);
        } catch (NullPointerException e) {
            System.out.println("Caught NullPointerException: " + e.getMessage());
        }
    }
}

Output

Caught NullPointerException: name is marked non-null but is null
Caught NullPointerException: email is marked non-null but is null

Explanation: The @NonNull annotation generates a null check for the annotated parameter or field and throws a NullPointerException if the value is null.

@SneakyThrows

The @SneakyThrows annotation allows you to throw checked exceptions without declaring them in the method signature.

Example

import lombok.SneakyThrows;

public class FileUtils {

    @SneakyThrows
    public static void readFile(String path) {
        if (path == null) {
            throw new java.io.IOException("Path cannot be null");
        }
        // Simulate file reading
        System.out.println("Reading file: " + path);
    }
}

public class LombokExample {
    public static void main(String[] args) {
        FileUtils.readFile("file.txt");

        try {
            FileUtils.readFile(null);
        } catch (Exception e) {
            System.out.println("Caught Exception: " + e.getMessage());
        }
    }
}

Output

Reading file: file.txt
Caught Exception: Path cannot be null

Explanation: The @SneakyThrows annotation allows the method to throw checked exceptions without declaring them in the method signature.

@Synchronized

The @Synchronized annotation ensures that a method is synchronized, similar to the synchronized keyword but with added benefits, such as avoiding issues with the this reference.

Example

import lombok.Synchronized;

public class Counter {
    private int count = 0;

    @Synchronized
    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class LombokExample {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.increment();
        System.out.println("Count: " + counter.getCount());
    }
}

Output

Count: 1

Explanation: The @Synchronized annotation synchronizes the method, ensuring thread safety.

@Getter(lazy=true)

The @Getter(lazy=true) annotation generates a lazy-initialized getter method, meaning the value is computed and cached on the first call.

Example

import lombok.Getter;

public class ExpensiveResource {
    @Getter(lazy=true)
    private final String resource = computeResource();

    private String computeResource() {
        System.out.println("Computing resource...");
        return "Expensive Resource";
    }
}

public class LombokExample {
    public static void main(String[] args) {
        ExpensiveResource resource = new ExpensiveResource();
        System.out.println("Resource: " + resource.getResource());
        System.out.println("Resource: " + resource.getResource());
    }
}

Output

Computing resource...
Resource: Expensive Resource
Resource: Expensive Resource

Explanation: The @Getter(lazy=true) annotation ensures the resource field is computed and cached on the first call to getResource(), improving performance for expensive computations.

Using Lombok in Spring Boot

Lombok integrates seamlessly with Spring Boot applications, reducing boilerplate code and simplifying development.

Example: Lombok in a Spring Boot Application

Spring Boot Application

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LombokSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(LombokSpringBootApplication.class, args);
    }
}

Defining Entities with Lombok

import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Data
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;
    private String lastName;
    private String email;
}

Defining Repositories

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

Defining Services with Lombok

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;

    @Transactional
    public User saveUser(User user) {
        return userRepository.save(user);
    }

    public User getUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

Defining Controllers with Lombok

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.saveUser(user);
    }

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUser(id);
    }
}

Testing the Spring Boot Application

You can test the REST API using a tool like Postman. Make a POST request to http://localhost:8080/users with the following JSON body:

{
    "firstName": "Amit",
    "lastName": "Sharma",
    "email": "amit.sharma@example.com"
}

Output

The response will be:

{
    "id": 1,
    "firstName": "Amit",
    "lastName": "Sharma",
    "email": "amit.sharma@example.com"
}

You can then make a GET request to http://localhost:8080/users/1 to retrieve the user details.

Output

{
    "id": 1,
    "firstName": "Amit",
    "lastName": "Sharma",
    "email": "amit.sharma@example.com"
}

Explanation: This example demonstrates how Lombok annotations can simplify the development of a Spring Boot application by reducing boilerplate code in entities, services, and controllers. The @Data, @NoArgsConstructor, @RequiredArgsConstructor, and other Lombok annotations help create clean and concise code.

Conclusion

Lombok is a powerful tool for reducing boilerplate code in Java applications. This guide covered the basics and advanced features of Lombok, including generating getters, setters, toString, equals, hashCode, constructors, custom mappings, nested classes, immutable objects, and using Lombok in Spring Boot applications. 

By leveraging Lombok, you can make your Java code more concise and maintainable. For more detailed information and advanced features, refer to the official Lombok documentation.

Comments