Skip to content
C Codeloom
Java

Java Records vs Lombok

Records and Lombok both reduce boilerplate, but they solve overlapping problems in very different ways. Here is when to pick which, with concrete tradeoffs.

·5 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • What records give you out of the box
  • How Lombok generates code at compile time
  • Where the two overlap and differ
  • Migration strategies and gotchas
  • Practical guidelines for new code

Prerequisites

  • Basic familiarity with the language

Every Java team eventually fights the same battle: writing data carriers without drowning in getters, setters, equals, and hashCode. For years, Lombok was the answer. Then Java 16 shipped records as a language feature. The question now is not whether to reduce boilerplate, but which tool to use for which job.

What records are

A record is a special kind of class designed for transparent, immutable data. You declare its components in the header and the compiler generates everything else: a canonical constructor, accessors, equals, hashCode, and toString.

public record Point(int x, int y) {}

Point p = new Point(3, 4);
p.x();              // 3
p.equals(new Point(3, 4)); // true

Records are final. Their components are final. You can add static factory methods, instance methods, and a compact constructor for validation.

public record Email(String value) {
    public Email {
        if (!value.contains("@")) {
            throw new IllegalArgumentException("invalid email");
        }
    }
}

What Lombok is

Lombok is a library that uses annotation processing to inject methods into classes at compile time. You annotate a class with @Data, @Value, @Builder, @Slf4j, and the bytecode comes out with the methods you asked for, even though the source file does not contain them.

@Value
public class Point {
    int x;
    int y;
}

That @Value class is roughly equivalent to a record. But Lombok also offers mutable variants (@Data), builders, lazy getters, delegation, logging, and partial generation through individual annotations like @Getter, @Setter, @EqualsAndHashCode.

The mental model

The two tools live at different layers of the stack.

Lombok:  source ---> [annotation processor injects bytecode] ---> .class
Records: source ---> [javac understands 'record' natively  ] ---> .class
Where records and Lombok plug in

Records are a language feature with strict semantics. Lombok is a build-time code generator with whatever semantics its annotations happen to offer this release. That difference shows up everywhere.

Where they overlap

Both can replace the classic immutable value object. Compare:

// Record
public record Money(BigDecimal amount, Currency currency) {}

// Lombok
@Value
public class Money {
    BigDecimal amount;
    Currency currency;
}

For this case, the record is strictly better. No dependency, no annotation processor, and the semantics are part of the language spec rather than a third-party library.

Where they differ

Records cannot extend other classes (they implicitly extend java.lang.Record). They cannot have non-static instance fields beyond their components. They are always final. If you need a mutable POJO, a class with inheritance, or a builder with twenty optional fields, records will not get you there.

Lombok handles those cases.

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserDto {
    private String name;
    private String email;
    private List<String> roles;
}

UserDto u = UserDto.builder()
    .name("Ada")
    .email("ada@example.com")
    .roles(List.of("admin"))
    .build();

Records have no built-in builder. You can add one manually, but it is boilerplate, which is exactly what you were trying to avoid.

Common pitfalls

With records: forgetting that the components are exposed by accessors named after them, not as getX. Frameworks that reflect on getXxx will not find anything unless they understand records. Jackson, Hibernate, and Spring have added support, but older versions or custom mappers may not.

With Lombok: invisible code. A bug in generated equals is hard to spot because the source file does not show the implementation. IDE tooling helps, but new team members are often confused about what @Data actually does.

With both: serialization frameworks may have edge cases. Test round-trips early.

Practical tips

For new code, default to records when the type is a simple, immutable data carrier. The language guarantees are stronger and there is no dependency.

Reach for Lombok when you genuinely need what it offers: builders for wide DTOs, mutable POJOs for JPA entities (where Hibernate insists on a no-arg constructor and field access), @Slf4j to standardize loggers, or delegation. Even then, prefer the narrow annotations (@Getter, @Builder) over @Data, which couples mutability and accessors and is easy to misuse.

Do not mix the two in the same class. A record with Lombok annotations is a smell. Pick one shape and stick with it.

For JPA entities specifically, records are still awkward in 2026 because of identity, lazy loading, and reflection requirements. Most teams use plain classes (with or without Lombok) for entities and use records for query results, DTOs, and value objects.

Wrap-up

Records win on simplicity and language support; Lombok wins on flexibility and breadth. The healthiest codebases use records for the 80 percent case of small immutable data and reserve Lombok for the cases records cannot cover. Treat boilerplate reduction as a means, not the goal: the real win is code that says exactly what the type is and nothing more.