CODE WITH SIBIN

Solving Real Problems with Real Code


Master Lombok Annotations in Spring Boot – Full Tutorial

Lombok is a Java library that helps reduce boilerplate code by automatically generating common constructs like getters, setters, constructors, and more through annotations. When combined with Spring Boot, it can significantly simplify your codebase.

Table of Contents

  1. Setup Lombok in Spring Boot
  2. Core Lombok Annotations
  3. Spring-Specific Lombok Annotations
  4. Logging Annotations
  5. Best Practices
  6. Complete Example

1. Setup Lombok in Spring Boot

Maven Dependency

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version> <!-- Use latest version -->
    <scope>provided</scope>
</dependency>

IDE Setup

  • Install Lombok plugin for your IDE (IntelliJ, Eclipse, etc.)
  • Enable annotation processing in your IDE settings

2. Core Lombok Annotations

@Getter and @Setter

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class User {
    private Long id;
    private String username;
    private String email;
    private boolean active;
}

// Equivalent to writing all getters and setters

@ToString

import lombok.ToString;

@ToString
public class User {
    private Long id;
    private String username;
    // ...
}

// Generates: public String toString() { return "User(id=" + this.id + ", username=" + this.username + ")"; }

@EqualsAndHashCode

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class User {
    private Long id;
    private String username;
    // ...
}

// Generates equals() and hashCode() methods

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

import lombok.*;

@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
public class User {
    @NonNull private Long id;
    private String username;
    private String email;
}

// Usage:
// User u1 = new User(); // NoArgsConstructor
// User u2 = new User(1L); // RequiredArgsConstructor (for @NonNull fields)
// User u3 = new User(1L, "john", "john@example.com"); // AllArgsConstructor

@Data

Combines @Getter@Setter@ToString@EqualsAndHashCode, and @RequiredArgsConstructor

import lombok.Data;

@Data
public class Product {
    private Long id;
    private String name;
    private double price;
}

@Builder

import lombok.Builder;

@Builder
public class Order {
    private Long id;
    private String productName;
    private int quantity;
    private double price;
}

// Usage:
// Order order = Order.builder()
//     .id(1L)
//     .productName("Laptop")
//     .quantity(1)
//     .price(999.99)
//     .build();

@Value

Immutable version of @Data (all fields are made private and final)

import lombok.Value;

@Value
public class ImmutableUser {
    Long id;
    String username;
    String email;
}

@Slf4j

(See logging section below)

3. Spring-Specific Lombok Annotations

@RequiredArgsConstructor + @Autowired

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    
    // Constructor with all final fields is automatically generated
    // and @Autowired is automatically applied in Spring
}

@With

Creates a clone of the object with one field changed (useful for immutable objects)

import lombok.With;

@With
public class ImmutableConfig {
    private final String host;
    private final int port;
}

// Usage:
// ImmutableConfig config = new ImmutableConfig("localhost", 8080);
// ImmutableConfig newConfig = config.withPort(9090);

4. Logging Annotations

@Slf4j

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class OrderService {
    public void processOrder(Order order) {
        log.info("Processing order: {}", order);
        try {
            // business logic
        } catch (Exception e) {
            log.error("Failed to process order {}", order.getId(), e);
        }
    }
}

Other logging variants:

  • @CommonsLog (Apache Commons Logging)
  • @Log (java.util.logging)
  • @Log4j (Log4j)
  • @Log4j2 (Log4j2)
  • @XSlf4j (Extended SLF4J Logger)

5. Best Practices

  1. Use @Data carefully - It generates all setters which might not be desired for immutable objects
  2. Prefer @RequiredArgsConstructor for Spring beans - Makes dependency injection clear
  3. Combine @Value with @Builder for immutable DTOs - Provides both immutability and easy construction
  4. Avoid Lombok for complex methods - If method logic isn't straightforward, implement it manually
  5. Be explicit with @ToString exclusions - Use @ToString.Exclude for sensitive data
  6. Document Lombok usage - Not all developers may be familiar with Lombok

6. Complete Example: Spring Boot Application with Lombok

Entity Class

import lombok.*;

import javax.persistence.*;
import java.time.LocalDateTime;

@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BlogPost {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String title;
    
    @Column(nullable = false, length = 5000)
    private String content;
    
    private String author;
    
    @Column(updatable = false)
    private LocalDateTime createdAt;
    
    private LocalDateTime updatedAt;
    
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
    }
    
    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

Repository Interface

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

public interface BlogPostRepository extends JpaRepository<BlogPost, Long> {
}

Service Layer

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

import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class BlogPostService {
    private final BlogPostRepository blogPostRepository;
    
    public BlogPost createPost(BlogPost post) {
        log.info("Creating new blog post: {}", post.getTitle());
        return blogPostRepository.save(post);
    }
    
    public List<BlogPost> getAllPosts() {
        return blogPostRepository.findAll();
    }
    
    public BlogPost getPostById(Long id) {
        return blogPostRepository.findById(id)
            .orElseThrow(() -> new PostNotFoundException("Post not found with id: " + id));
    }
    
    public BlogPost updatePost(Long id, BlogPost postDetails) {
        BlogPost post = getPostById(id);
        post.setTitle(postDetails.getTitle());
        post.setContent(postDetails.getContent());
        post.setAuthor(postDetails.getAuthor());
        return blogPostRepository.save(post);
    }
    
    public void deletePost(Long id) {
        blogPostRepository.deleteById(id);
    }
}

@RequiredArgsConstructor
class PostNotFoundException extends RuntimeException {
    private final String message;
}

Controller

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.List;

@RestController
@RequestMapping("/api/posts")
@RequiredArgsConstructor
public class BlogPostController {
    private final BlogPostService blogPostService;
    
    @GetMapping
    public ResponseEntity<List<BlogPost>> getAllPosts() {
        return ResponseEntity.ok(blogPostService.getAllPosts());
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<BlogPost> getPostById(@PathVariable Long id) {
        return ResponseEntity.ok(blogPostService.getPostById(id));
    }
    
    @PostMapping
    public ResponseEntity<BlogPost> createPost(@RequestBody BlogPost post) {
        BlogPost createdPost = blogPostService.createPost(post);
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(createdPost.getId())
            .toUri();
        return ResponseEntity.created(location).body(createdPost);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<BlogPost> updatePost(@PathVariable Long id, @RequestBody BlogPost post) {
        return ResponseEntity.ok(blogPostService.updatePost(id, post));
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deletePost(@PathVariable Long id) {
        blogPostService.deletePost(id);
        return ResponseEntity.noContent().build();
    }
}

DTO (Data Transfer Object)

import lombok.Value;
import lombok.Builder;

@Value
@Builder
public class BlogPostSummaryDto {
    Long id;
    String title;
    String author;
    
    public static BlogPostSummaryDto fromEntity(BlogPost post) {
        return BlogPostSummaryDto.builder()
            .id(post.getId())
            .title(post.getTitle())
            .author(post.getAuthor())
            .build();
    }
}

Configuration Class

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class BlogConfig {
    @Bean
    CommandLineRunner initDatabase(BlogPostRepository repository) {
        return args -> {
            log.info("Preloading sample data...");
            repository.save(BlogPost.builder()
                .title("First Post")
                .content("This is my first blog post")
                .author("John Doe")
                .build());
            
            repository.save(BlogPost.builder()
                .title("Second Post")
                .content("Another interesting post")
                .author("Jane Smith")
                .build());
        };
    }
}

Conclusion

Lombok significantly reduces boilerplate code in Spring Boot applications, making your code more concise and readable. By combining Lombok's annotations with Spring's features, you can create clean, maintainable applications with minimal repetitive code.

Remember to:

  1. Properly configure your IDE for Lombok
  2. Use the appropriate annotations for each use case
  3. Be mindful of the generated code (especially with @Data and @EqualsAndHashCode)
  4. Document Lombok usage in your project for team members who may not be familiar with it

With these tools, you can focus more on business logic and less on repetitive Java syntax.

Leave a Reply

Your email address will not be published. Required fields are marked *