Project Lombok
Project Lombok is a Java library that automatically plugs into your editor and build tools, spicing up your Java code. It helps you eliminate boilerplate code like getters, setters, constructors, equals, hashCode, toString methods, and more through annotations.
IDE Setup
Section titled “IDE Setup”VSCode:
1. Core Annotations
Section titled “1. Core Annotations”@Getter and @Setter
Section titled “@Getter and @Setter”import lombok.Getter;import lombok.Setter;
public class User { @Getter @Setter private String username;
@Getter @Setter private String email;
@Setter(AccessLevel.PROTECTED) private String password;
@Getter(AccessLevel.PUBLIC) private final String userId = "generated-id";}
// Without Lombokclass UserWithoutLombok { private String username; private String email; private String password; private final String userId = "generated-id";
public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } protected void setPassword(String password) { this.password = password; } public String getUserId() { return userId; }}@ToString
Section titled “@ToString”import lombok.ToString;
@ToStringpublic class Product { private String name; private double price; private String category;
@ToString.Exclude private String secretCode;
@ToString.Include(name = "formattedPrice") private String getFormattedPrice() { return "$" + price; }}
// UsageProduct product = new Product();product.setName("Laptop");product.setPrice(999.99);product.setCategory("Electronics");System.out.println(product);// Output: Product(name=Laptop, price=999.99, category=Electronics, formattedPrice=$999.99)@EqualsAndHashCode
Section titled “@EqualsAndHashCode”import lombok.EqualsAndHashCode;
@EqualsAndHashCodepublic class Employee { private String employeeId; private String name;
@EqualsAndHashCode.Exclude private String temporaryCode;}
// Equivalent to:class EmployeeWithoutLombok { private String employeeId; private String name; private String temporaryCode;
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; EmployeeWithoutLombok that = (EmployeeWithoutLombok) o; return Objects.equals(employeeId, that.employeeId) && Objects.equals(name, that.name); }
@Override public int hashCode() { return Objects.hash(employeeId, name); }}@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor
Section titled “@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor”import lombok.*;
@NoArgsConstructor@AllArgsConstructor@RequiredArgsConstructorpublic class Person { @NonNull private String name;
private int age;
private final String country = "USA";}
// Usage examples:Person p1 = new Person(); // NoArgsConstructorPerson p2 = new Person("John", 25); // AllArgsConstructorPerson p3 = new Person("Jane"); // RequiredArgsConstructor (only @NonNull fields)The @Data annotation is a convenient shortcut that bundles:
- @Getter
- @Setter
- @ToString
- @EqualsAndHashCode
- @RequiredArgsConstructor
import lombok.Data;
@Datapublic class Student { private final String studentId; private String name; private int age; private String email;}
// Equivalent to:class StudentWithoutLombok { private final String studentId; private String name; private int age; private String email;
public StudentWithoutLombok(String studentId) { this.studentId = studentId; }
// Getters and setters for all non-final fields // equals(), hashCode(), toString() methods}@Builder
Section titled “@Builder”import lombok.Builder;import lombok.Singular;import java.util.List;
@Builderpublic class Order { private String orderId; private String customerName; private double totalAmount;
@Singular private List<String> items;
@Builder.Default private String status = "PENDING";}
// Usage:Order order = Order.builder() .orderId("ORD123") .customerName("John Doe") .totalAmount(199.99) .item("Laptop") .item("Mouse") .item("Keyboard") .build();
// Custom builder method@Builder(builderMethodName = "internalBuilder", buildMethodName = "create")class CustomBuilderExample { private String value;
public static class CustomBuilderExampleBuilder { public CustomBuilderExample create() { // Custom validation if (value == null) { throw new IllegalStateException("Value cannot be null"); } return new CustomBuilderExample(this); } }}@Value
Section titled “@Value”@Value creates immutable classes (similar to @Data but for immutable objects)
import lombok.Value;import lombok.With;
@Value@Builderpublic class ImmutableUser { String username; String email; @With int age;}
// Usage:ImmutableUser user = ImmutableUser.builder() .username("john") .email("john@example.com") .age(25) .build();
// Create a new instance with modified ageImmutableUser olderUser = user.withAge(26);2. Advanced Features
Section titled “2. Advanced Features”@Slf4j, @Log, etc.
Section titled “@Slf4j, @Log, etc.”import lombok.extern.slf4j.Slf4j;
@Slf4jpublic class LoggingExample { public void processData() { log.info("Processing started"); try { // business logic log.debug("Debug information"); } catch (Exception e) { log.error("Error occurred", e); } }}
// Equivalent to:class LoggingExampleWithoutLombok { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LoggingExampleWithoutLombok.class);
public void processData() { log.info("Processing started"); // ... same code }}@SneakyThrows
Section titled “@SneakyThrows”import lombok.SneakyThrows;import java.io.*;
public class FileProcessor {
@SneakyThrows(IOException.class) public String readFile(String path) { try (BufferedReader reader = new BufferedReader(new FileReader(path))) { return reader.readLine(); } }
@SneakyThrows // Throws any exception that occurs public void riskyMethod() { throw new Exception("This exception is sneaky!"); }}
// Equivalent to:class FileProcessorWithoutLombok { public String readFile(String path) { try { try (BufferedReader reader = new BufferedReader(new FileReader(path))) { return reader.readLine(); } } catch (IOException e) { throw new RuntimeException(e); } }}@Synchronized
Section titled “@Synchronized”import lombok.Synchronized;
public class SynchronizedExample { private final Object readLock = new Object();
@Synchronized public void method1() { // synchronized on 'this' }
@Synchronized("readLock") public void method2() { // synchronized on readLock object }}
// Equivalent to:class SynchronizedExampleWithoutLombok { private final Object readLock = new Object();
public void method1() { synchronized (this) { // method body } }
public void method2() { synchronized (readLock) { // method body } }}@Cleanup
Section titled “@Cleanup”import lombok.Cleanup;import java.io.*;
public class CleanupExample { public void copyFile(String source, String destination) throws IOException { @Cleanup InputStream in = new FileInputStream(source); @Cleanup OutputStream out = new FileOutputStream(destination);
byte[] buffer = new byte[1024]; int length; while ((length = in.read(buffer)) != -1) { out.write(buffer, 0, length); } }}
// Equivalent to:class CleanupExampleWithoutLombok { public void copyFile(String source, String destination) throws IOException { InputStream in = new FileInputStream(source); try { OutputStream out = new FileOutputStream(destination); try { byte[] buffer = new byte[1024]; int length; while ((length = in.read(buffer)) != -1) { out.write(buffer, 0, length); } } finally { if (out != null) { out.close(); } } } finally { if (in != null) { in.close(); } } }}3. Configuration and Customization
Section titled “3. Configuration and Customization”lombok.config
Section titled “lombok.config”Create a lombok.config file in your project root:
# Global configurationlombok.anyConstructor.suppressConstructorProperties = truelombok.addLombokGeneratedAnnotation = true
# Make @Data entities more JPA-friendlylombok.data.ignoreNull = true
# Logging configurationlombok.log.fieldName = LOGGERlombok.log.fieldIsStatic = false
# Warning configurationlombok.experimental.flagUsage = WARNINGField Name Constants
Section titled “Field Name Constants”import lombok.experimental.FieldNameConstants;
@FieldNameConstantspublic class ConstantsExample { private String firstName; private String lastName; private int age;}
// Usage:String fieldName = ConstantsExample.Fields.firstName; // "firstName"4. Practical Examples
Section titled “4. Practical Examples”JPA Entity with Lombok
Section titled “JPA Entity with Lombok”import lombok.*;import javax.persistence.*;import java.time.LocalDateTime;
@Entity@Table(name = "users")@Data@Builder@NoArgsConstructor@AllArgsConstructor@EqualsAndHashCode(onlyExplicitlyIncluded = true)public class User {
@Id @EqualsAndHashCode.Include @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@Column(unique = true, nullable = false) private String username;
@Column(nullable = false) private String email;
@Column(nullable = false) private String password;
@Builder.Default private boolean active = true;
@Builder.Default private LocalDateTime createdAt = LocalDateTime.now();
// Custom getter public String getDisplayName() { return username + " (" + email + ")"; }}DTO with Lombok
Section titled “DTO with Lombok”import lombok.Value;
@Value@Builderpublic class UserDTO { String username; String email; boolean active; String displayName;
public static UserDTO fromEntity(User user) { return UserDTO.builder() .username(user.getUsername()) .email(user.getEmail()) .active(user.isActive()) .displayName(user.getDisplayName()) .build(); }}Service Class with Lombok
Section titled “Service Class with Lombok”import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;
@Service@Slf4j@RequiredArgsConstructorpublic class UserService {
private final UserRepository userRepository; private final EmailService emailService;
public User createUser(CreateUserRequest request) { log.info("Creating user: {}", request.getUsername());
User user = User.builder() .username(request.getUsername()) .email(request.getEmail()) .password(request.getPassword()) .build();
User savedUser = userRepository.save(user); emailService.sendWelcomeEmail(savedUser.getEmail());
return savedUser; }}5. Common Patterns and Best Practices
Section titled “5. Common Patterns and Best Practices”Immutable Configuration Classes
Section titled “Immutable Configuration Classes”@Value@Builder@ConfigurationProperties(prefix = "app.database")public class DatabaseConfig { String url; String username; String password; int poolSize;
@Builder.Default boolean sslEnabled = false;
@Builder.Default int timeout = 30;}Builder with Validation
Section titled “Builder with Validation”@Data@Builderpublic class ValidatedUser { @NotBlank private String username;
@Email private String email;
@Min(18) private int age;
public static class ValidatedUserBuilder { public ValidatedUser build() { ValidatedUser user = new ValidatedUser(this); // Custom validation if (user.getUsername().length() < 3) { throw new IllegalArgumentException("Username must be at least 3 characters"); } return user; } }}Record-like Classes (Pre-Java 16)
Section titled “Record-like Classes (Pre-Java 16)”@Value@Builder@AllArgsConstructorpublic class Point { int x; int y;
public Point withX(int newX) { return new Point(newX, this.y); }
public Point withY(int newY) { return new Point(this.x, newY); }}6. Testing with Lombok
Section titled “6. Testing with Lombok”Test Classes
Section titled “Test Classes”import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.extension.ExtendWith;import org.mockito.junit.jupiter.MockitoExtension;
@Slf4j@ExtendWith(MockitoExtension.class)class UserServiceTest {
@Test void shouldCreateUserSuccessfully() { // Given CreateUserRequest request = CreateUserRequest.builder() .username("testuser") .email("test@example.com") .password("password") .build();
log.info("Testing user creation with: {}", request);
// When & Then // ... test implementation }}7. Common Pitfalls and Solutions
Section titled “7. Common Pitfalls and Solutions”Circular References in @ToString
Section titled “Circular References in @ToString”// Problematic:@Dataclass Department { private String name; private List<Employee> employees;}
@Dataclass Employee { private String name; private Department department; // Circular reference!}
// Solution:@Dataclass Department { private String name;
@ToString.Exclude private List<Employee> employees;}
@Dataclass Employee { private String name;
@ToString.Exclude private Department department;}Inheritance Issues
Section titled “Inheritance Issues”// Base class@Dataclass BaseEntity { private Long id; private LocalDateTime createdAt;}
// Child class - PROBLEMATIC@Dataclass User extends BaseEntity { private String username; // Missing BaseEntity fields in equals/hashCode/toString!}
// Solution:@Data@EqualsAndHashCode(callSuper = true)@ToString(callSuper = true)class User extends BaseEntity { private String username;}8. Lombok with Spring Boot
Section titled “8. Lombok with Spring Boot”Complete Spring Boot Example
Section titled “Complete Spring Boot Example”// Entity@Entity@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name; private String description; private BigDecimal price;
@Builder.Default private Boolean active = true;}
// Repositorypublic interface ProductRepository extends JpaRepository<Product, Long> {}
// Service@Service@RequiredArgsConstructor@Slf4jpublic class ProductService { private final ProductRepository productRepository;
public Product createProduct(ProductDTO productDTO) { log.info("Creating product: {}", productDTO.getName());
Product product = Product.builder() .name(productDTO.getName()) .description(productDTO.getDescription()) .price(productDTO.getPrice()) .build();
return productRepository.save(product); }
public ProductDTO toDTO(Product product) { return ProductDTO.builder() .id(product.getId()) .name(product.getName()) .description(product.getDescription()) .price(product.getPrice()) .active(product.getActive()) .build(); }}
// DTO@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic class ProductDTO { private Long id;
@NotBlank private String name;
private String description;
@Positive private BigDecimal price;
private Boolean active;}
// Controller@RestController@RequestMapping("/api/products")@RequiredArgsConstructor@Slf4jpublic class ProductController { private final ProductService productService;
@PostMapping public ResponseEntity<ProductDTO> createProduct(@Valid @RequestBody ProductDTO productDTO) { Product product = productService.createProduct(productDTO); return ResponseEntity.ok(productService.toDTO(product)); }}9. Migration Tips
Section titled “9. Migration Tips”Before Lombok
Section titled “Before Lombok”public class User { private String name; private String email; private int age;
public User() {}
public User(String name, String email, int age) { this.name = name; this.email = email; this.age = age; }
// 20+ lines of boilerplate...}After Lombok
Section titled “After Lombok”@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class User { private String name; private String email; private int age;}Benefits of Using Lombok
Section titled “Benefits of Using Lombok”- Reduced Boilerplate: 50-80% less code
- Improved Readability: Focus on business logic
- Fewer Bugs: Auto-generated methods are reliable
- Easy Refactoring: Change fields, methods auto-update
- Better Maintenance: Consistent code generation
When NOT to Use Lombok
Section titled “When NOT to Use Lombok”- Library Development: Forces dependency on users
- Complex Business Logic: Manual implementation might be better
- Team Resistance: If team prefers explicit code
- Annotation Overload: Too many annotations can reduce readability