33. Records vs Lombok
Records are the standard Java alternative to Lombok @Value; records cannot have mutable fields or extend classes, but they require no annotation processing and are a first-class language feature.
Explanation
Lombok @Value generates an immutable class with final fields, a constructor, getters, equals(), hashCode(), and toString() via annotation processing. Records do the same natively, with zero tooling dependencies.
Records cannot: extend other classes (they extend Record implicitly), have mutable (non-final) fields, or have setter methods. Records can: implement interfaces, have custom methods, use generic type parameters, and appear in switch patterns and deconstruction patterns.
Lombok has some features records do not: @Builder (rich builder generation), @Data (mutable data class), @SuperBuilder (inheritance-aware builders), and @EqualsAndHashCode(callSuper=true). For complex mutable DTOs, Lombok still has a role. For pure immutable value types, prefer records โ no IDE plugin, no annotation processor, less magic.
Example
// Lombok (hypothetical โ requires Lombok dependency):
// @Value
// public class LombokUser {
// String name;
// int age;
// }
// LombokUser u = new LombokUser("Alice", 30);
// u.getName(); u.getAge(); u.equals(...); u.hashCode(); u.toString();
public class RecordsVsLombok {
// Native record: same result, no dependency
record User(String name, int age) {}
// Record + manual builder (alternative to @Builder)
record Config(String host, int port, boolean ssl) {
static Builder builder() { return new Builder(); }
static class Builder {
private String host = "localhost";
private int port = 8080;
private boolean ssl = false;
Builder host(String h) { this.host = h; return this; }
Builder port(int p) { this.port = p; return this; }
Builder ssl(boolean s) { this.ssl = s; return this; }
Config build() { return new Config(host, port, ssl); }
}
}
// Records support interfaces โ Lombok @Value does not easily
interface Displayable { String display(); }
record Product(String name, double price) implements Displayable {
@Override public String display() {
return String.format("%s โ $%.2f", name, price);
}
}
public static void main(String[] args) {
User u = new User("Alice", 30);
System.out.println(u); // User[name=Alice, age=30]
System.out.println(u.name()); // Alice (not getName())
System.out.println(u.age()); // 30
Config cfg = Config.builder()
.host("prod.example.com")
.port(443)
.ssl(true)
.build();
System.out.println(cfg);
Product p = new Product("Java Book", 49.99);
System.out.println(p.display());
// Pattern matching โ records integrate natively
Object obj = new User("Bob", 25);
if (obj instanceof User(String name, int age)) { // deconstruction pattern
System.out.println(name + " is " + age);
}
}
}Sources
- https://openjdk.org/jeps/395 โ JEP 395: Records
- https://projectlombok.org/features/Value โ Lombok @Value