DEV Community

Cover image for Why I Stopped Using Lombok’s `@Data` on JPA Entities
Ankit Sood
Ankit Sood

Posted on

Why I Stopped Using Lombok’s `@Data` on JPA Entities

A few days ago, I ran into a bug that didn’t look like a bug at all.

I was working on a fairly standard Spring Boot service, nothing unusual. A couple of entities, some relationships, and a small feature change. To keep things concise, I did what most of us do:

@Data
@Entity
public class User {
    ...
}
Enter fullscreen mode Exit fullscreen mode

It felt natural, clean and efficient.

The application started fine. APIs were working. Everything looked normal.

Until I added a simple log statement.


The Bug That Didn’t Make Sense

log.info("User details: {}", user);
Enter fullscreen mode Exit fullscreen mode

The moment this line executed, the application crashed with a StackOverflowError.

At first glance, it didn’t add up.

There was no recursion in my code. No complex logic. Just a log statement.

But as I traced through the stack, the pattern became clear.


When toString() Becomes a Trap

The User entity had a bidirectional relationship:

  • A User had a collection of Orders
  • Each Order referenced back to the User

This is a very common JPA pattern.

What I hadn’t paid attention to was what Lombok’s @Data had generated for me.

It includes a toString() method that prints all fields, including relationships.

So when I logged the User:

  • User.toString() tried to print orders
  • Each Order tried to print its user
  • Which again tried to print orders

And so on.

An infinite loop—triggered by logging.


The Subtle Performance Issue

While investigating, I noticed something else that was even more concerning.

In another part of the code, I had something like:

Set<User> users = new HashSet<>();
users.add(user);
Enter fullscreen mode Exit fullscreen mode

Nothing unusual.

But it was unexpectedly slow.

The reason, again, traced back to @Data.

It generates equals() and hashCode() using all fields. In a JPA entity, that can be problematic especially when those fields include lazily loaded relationships.

To compute the hash, Hibernate ended up initializing those lazy fields, which meant:

  • Additional database queries
  • More data loaded than expected
  • All happening implicitly

No explicit query. No clear signal in the code.


The Core Issue

The root of the problem is simple:

JPA entities are not plain Java objects.

They are managed by a persistence context, often proxied, and frequently only partially loaded.

Lombok, however, generates code as if everything is a simple POJO. That disconnect is where these issues originate.


What I Changed

I didn’t stop using Lombok. But I stopped using @Data on entities.

Instead, I now prefer:

@Getter
@Setter
@Entity
public class User {
    ...
}
Enter fullscreen mode Exit fullscreen mode

For methods like toString(), equals(), and hashCode(), I either:

  • Write them explicitly, or
  • Use Lombok annotations selectively (e.g., excluding relationships)

This keeps behavior predictable and avoids unintended side effects.


Takeaway

@Data is incredibly useful in the right places.

But JPA entities are not one of them.

What looks like a small convenience can introduce:

  • Infinite recursion
  • Hidden database queries
  • Hard-to-debug behavior

All without any obvious warning.


Final Thought

This wasn’t a complex failure. It didn’t require scale or load to surface.

It came from a perfectly normal development flow and that’s exactly why it’s worth paying attention to.

Sometimes the most dangerous problems aren’t the ones we struggle to write. They’re the ones we don’t realize we’ve already written for us.

📚 Further Reading

  • Vlad Mihalcea — How to implement equals and hashCode using the JPA entity identifier

    https://vladmihalcea.com/hibernate-facts-equals-and-hashcode/

    A must-read on why JPA entity equality should be based on identifiers rather than all fields, and how improper implementations can lead to subtle bugs.

  • Project Lombok — @data Annotation Documentation

    https://projectlombok.org/features/Data

    Explains what @Data actually generates under the hood, which helps understand why its default behavior can conflict with JPA entities.

Top comments (0)