Records vs Classes vs Lombok
Compare records with traditional classes and Lombok annotations for choosing the right tool.
Records vs Classes vs Lombok is a free Java Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Records vs Classes vs Lombok
Java offers several approaches to creating data-carrying types: plain classes with boilerplate, Records (Java 16+), or Lombok annotations. Choosing the right one depends on the use case.
The Boilerplate Problem
A plain Java class for a simple data carrier requires significant boilerplate: constructor, getters, equals, hashCode, toString — all for just a few fields.
// Traditional class — lots of boilerplate
class ProductOld {
private final String id;
private final String name;
private final double price;
public ProductOld(String id, String name, double price) {
this.id = id; this.name = name; this.price = price;
}
public String getId() { return id; }
public String getName() { return name; }
public double getPrice() { return price; }
@Override public boolean equals(Object o) { /* ... */ }
@Override public int hashCode() { /* ... */ }
@Override public String toString() { /* ... */ }
}The Record Solution
A Record condenses the same class to one line. The compiler generates all the boilerplate automatically.
// Record — one line, same functionality
record Product(String id, String name, double price) {}
// Usage is almost identical except accessors use component names (no get prefix)
Product p = new Product("P001", "Laptop", 999.99);
System.out.println(p.id()); // P001 (not getId())
System.out.println(p.name()); // Laptop
System.out.println(p.price()); // 999.99
System.out.println(p); // Product[id=P001, name=Laptop, price=999.99]Lombok @Data and @Value
Lombok generates boilerplate at compile time via annotations. @Value creates an immutable class similar to records; @Data creates a mutable class.
// Lombok @Value — immutable (closest to record)
import lombok.Value;
@Value
public class LombokProduct {
String id;
String name;
double price;
// Lombok generates: all-args constructor, getters, equals, hashCode, toString
// Note: accessors follow getX() naming convention (getPrice(), not price())
}
// Lombok @Data — mutable
import lombok.Data;
@Data
public class MutableProduct {
private String id;
private String name;
private double price;
// Generates: getters, setters, equals, hashCode, toString, no-args constructor
}Key Differences
Important differences between Records, Lombok @Value, and plain classes:
- Accessor naming: Records use
name(); Lombok usesgetName() - Mutable variant: Records are always immutable; Lombok offers @Data for mutability
- Inheritance: Records cannot extend classes; Lombok classes can
- Build tool dependency: Lombok requires annotation processor setup; Records are built-in
When to Use Records
Records are ideal when:
- The type is primarily a transparent data carrier (DTO, Value Object)
- Immutability is desired by design
- You are on Java 16+ and want zero dependencies
- The type participates in pattern matching (sealed + records)
// Perfect record use cases:
record Coordinates(double lat, double lon) {}
record JwtClaims(String subject, String role, long expiresAt) {}
record PageRequest(int page, int size, String sortBy) {}
record ErrorResponse(int code, String message, String path) {}
// These are all simple data carriers with no mutable stateWhen to Use Lombok
Lombok is preferred when:
- You need mutable beans (e.g., JPA entities, Spring configuration classes)
- Accessor naming must follow JavaBeans convention (
getX) for compatibility with frameworks - You need
@Builderfor complex object construction - Partial mutability is needed (
@NonFinalon some fields)
// Lombok @Builder — great for complex object construction
import lombok.*;
@Builder
@Value
public class EmailMessage {
String to;
String from;
String subject;
String body;
List<String> attachments;
}
EmailMessage msg = EmailMessage.builder()
.to("user@example.com")
.from("noreply@app.com")
.subject("Welcome!")
.body("Thank you for signing up")
.attachments(List.of())
.build();When to Use Plain Classes
Plain classes remain appropriate when:
- The class has complex behavior beyond simple data carrying
- You need to extend another class
- Fine-grained control over hashCode/equals is needed
- Mutable state with encapsulation is required
// Plain class — when behavior dominates over data
class ShoppingCart {
private final List<CartItem> items = new ArrayList<>();
private final String customerId;
public ShoppingCart(String customerId) {
this.customerId = customerId;
}
public void addItem(CartItem item) { items.add(item); }
public void removeItem(String sku) { items.removeIf(i -> i.sku().equals(sku)); }
public double total() { return items.stream().mapToDouble(CartItem::lineTotal).sum(); }
public boolean isEmpty() { return items.isEmpty(); }
}Mixing Records and Classes
Records and classes work seamlessly together. Use records for immutable data inside classes that manage mutable state.
// Record for the immutable event data
record UserEvent(String userId, String action, java.time.Instant timestamp) {
public static UserEvent now(String userId, String action) {
return new UserEvent(userId, action, java.time.Instant.now());
}
}
// Class for the mutable event store
class EventLog {
private final List<UserEvent> events = new ArrayList<>();
public void record(String userId, String action) {
events.add(UserEvent.now(userId, action));
}
public List<UserEvent> eventsFor(String userId) {
return events.stream()
.filter(e -> e.userId().equals(userId)).toList();
}
}Records in Spring Boot
Records integrate well with Spring Boot as request/response bodies. Spring's Jackson mapper handles them with minimal configuration.
// Spring Boot REST controller using records
import org.springframework.web.bind.annotation.*;
record CreateProductRequest(String name, double price, String category) {}
record ProductResponse(long id, String name, double price, String category) {}
@RestController
@RequestMapping("/api/products")
class ProductController {
@PostMapping
public ProductResponse create(@RequestBody CreateProductRequest req) {
// Jackson automatically deserializes JSON into the record
long id = productService.save(req);
return new ProductResponse(id, req.name(), req.price(), req.category());
}
}Performance Comparison
All three approaches produce essentially the same bytecode for simple data carriers. Performance is identical — the choice is about developer experience and requirements.
// All three compile to approximately equivalent bytecode:
// - Records: compiler-generated, always immutable
// - Lombok @Value: annotation-processor-generated, always immutable
// - Plain class: hand-written
// The key runtime difference:
// Records: accessor is a method matching field name (price() not getPrice())
// Lombok/Class: accessor follows JavaBeans (getPrice())
// Jackson compatibility:
// Records: work natively in Jackson 2.12+ with no config
// Lombok: work with @JsonProperty or jackson-databind-lombok moduleQuick Check
What naming convention do record accessors follow?
Recap: Records vs Classes vs Lombok
Key takeaways:
- Records auto-generate constructor, accessors (no get prefix), equals, hashCode, toString
- Records are always immutable; use plain classes for mutable state
- Lombok @Value approximates records but uses getX() naming and requires setup
- Lombok @Builder excels at complex object construction with many optional fields
- Records are ideal for DTOs, value objects, API request/response bodies
- Plain classes remain best when behavior dominates or inheritance is needed
Frequently asked questions
Is the “Records vs Classes vs Lombok” lesson free?
Yes — the full text of “Records vs Classes vs Lombok” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.
What will I learn in “Records vs Classes vs Lombok”?
Compare records with traditional classes and Lombok annotations for choosing the right tool. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Records vs Classes vs Lombok” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Introducing Records
- Compact Constructors and Validation
- Custom Methods on Records
- Records vs Classes vs Lombok