DEV Community

Cover image for Why Lombok Isn’t Saving You Code — It’s Costing You a Design
Leon Pennings
Leon Pennings

Posted on • Originally published at blog.leonpennings.com

Why Lombok Isn’t Saving You Code — It’s Costing You a Design

There is a persistent belief in the Java ecosystem that Lombok “saves time” by removing boilerplate — especially getters and setters. It’s an appealing idea: annotate a class, remove fifty lines of repetitive code, ship features faster. “Look how clean!” the demos say. “Look how much less code!”

But the cost is not measured in lines of code.

The cost is measured in architecture.

Lombok does not merely eliminate boilerplate; it eliminates design pressure. It encourages a programming style where objects stop being objects, where the domain loses meaning, and where logic migrates into procedural orchestration scripts. And the most common justification — “we only need simple data containers” — does not hold up once you examine it through the lens of object-oriented design or domain modelling.

In this article, we go beyond “Lombok is bad” and examine why its most popular feature — auto-generated getters and setters — actively undermines the principles we rely on to build maintainable, expressive, and evolvable systems.


1. The Myth of Boilerplate Reduction

The typical Lombok pitch goes like this:

“Java is verbose. Lombok reduces boilerplate. Just write your fields; Lombok writes your getters, setters, constructors, equals/hashCode, toString, and so on.”

But this reasoning contains a hidden assumption:

That the boilerplate being removed was ever necessary.

Getters and setters are only “boilerplate” if you assume all objects are nothing more than miniature data bags. But in a well-designed domain model, getters and setters are not boilerplate — they are the expression of the object’s business interface.

If an entity represents a business concept, it has meaning.

If it has meaning, its fields are not raw public state.

If its fields are not raw public state, then exposing them as if they are — through auto-generated accessors — betrays the abstraction.

Most of the boilerplate Lombok removes is boilerplate that should not exist in the first place.


2. The Real Cost: Objects Become Dumb Data Buckets

When you annotate a class with @Getter and @Setter, you are not expressing design intent. You are declaring:

“This thing has no behavior. Anybody can read or modify its state freely.”

This is not object-oriented design.

This is not domain modelling.

This is a struct with Java syntax.

It is the same anti-pattern Fowler described in 2003 as the Anemic Domain Model: entities with no behavior, with business logic stored elsewhere in services that manipulate them procedurally.

When every class becomes a DTO by default, the domain layer collapses. Meaning gets pushed upward into procedural flows:

  • validationService.validate(entity)

  • calculatorService.calculate(entity)

  • workflowService.update(entity)

  • enrichmentService.enrich(entity)

The services grow.

The domain shrinks.

Duplication spreads.

Lombok didn’t create anemic design — but it removes friction, and friction is often what prevents us from sliding into bad patterns.


3. Getters and Setters Are Not Neutral Code

There is a popular sentiment that getters and setters are “just accessors,” a harmless technical detail. But this is false. Auto-generated getters/setters are:

  • a contract

  • a stability promise

  • a piece of public API

You are committing to field-level access semantics in perpetuity — a commitment you often don’t recognize until it’s too late.

And because Lombok makes this commitment invisible, every field becomes part of your public interface by default. You’ve removed explicit design decisions and replaced them with implied ones.

Worse, you’ve removed the opportunity to question the existence of those accessors at all.

A setter is a statement: “This object has no invariant worth protecting.”

Does Lombok warn you when you violate invariants?

Does it know which values can be changed freely and which cannot?

Does it know which combinations of fields are meaningful?

No.

It generates a function because a field exists.

This is not design.

It is automation of accidental complexity.


4. Logic in Getters/Setters Is Not an Anti-Pattern — It’s Encapsulation

A common counterargument is:

“If you need logic in getters/setters, you can just write your own instead of using Lombok.”

This totally misses the point.

If getters and setters can contain logic, they are no longer “boilerplate.”

They are part of the business model.

Examples:

public Money totalPrice() { return price.times(quantity); }
public boolean isEnabled() { return !"false".equalsIgnoreCase(rawValue); }
public void setBirthDate(LocalDate d) { guardAgainstFuture(d); this.date = d; }
public String fullName() { return first + " " + last; }
Enter fullscreen mode Exit fullscreen mode

These are not implementation details.

These are meaning.

And meaning is the one thing Lombok prevents you from thinking about, because it shifts your mindset toward:

“A property is a field; access is just a getter/setter.”

When field access becomes automatic, the question “Should this be a method?” stops being asked. And when the question stops being asked, you lose the opportunity to shape a domain language.


5. Lombok Encourages Structural Thinking, Not Behavioural Thinking

Object-oriented design is fundamentally about behavior.

Lombok encourages the opposite: structural exposure.

Instead of messages between objects, you get:

  • data extraction

  • data transformation

  • data reinsertion

This is functional code masquerading as OO — except without the advantages of pure functional programming (immutability, expression trees, clear flows, easy transformation).

You get the worst of both worlds:

  • procedural orchestration

  • mutable records

  • leaking implementation details

  • distributed business logic across services

And because Lombok makes it so easy, you build this architecture without even noticing.


6. DTOs Are Already a Bypass — Lombok Makes It Worse

Even the legitimate place for pure data structures — DTOs — is often misunderstood.

DTOs are supposed to be:

  • translation objects

  • serialization boundaries

  • ephemeral

  • mapping layers between bounded contexts

DTOs are not part of the domain.

But most codebases use DTOs because their domain is already stripped of meaning. And Lombok makes DTOs so easy to generate that developers start using them as internal abstractions, not just boundary objects.

Now your system has:

  • DTOs at the boundaries

  • DTOs in the core

  • DTOs passed between services

  • DTOs as entities

  • DTOs as commands

Everywhere you look: structures.

Nowhere: objects.

And because DTOs have no behavior, the service layer takes over. Then you end up with 30 services all implementing overlapping logic because nothing in the model was allowed to protect or represent meaning.


7. Configuration Objects Prove the Problem, Not the Exception

One of the most common “exceptions” people claim is configuration classes:

“We just need getters for config — that’s not domain logic.”

But this is wrong for two reasons:

1. Configuration values have semantics.

  • "true" means “enabled”

  • "0" may mean “disabled”

  • "OFF" may mean “ignore this feature”

So instead of:

public String getValue();
Enter fullscreen mode Exit fullscreen mode

You almost always want:

public boolean isEnabled();
public Duration timeout();
public Mode mode();
Enter fullscreen mode Exit fullscreen mode

These aren’t raw fields.

They are meaning.

2. If a config object really contains raw strings, it shouldn’t pretend to be an object.

A map is adequate.

A properties file is adequate.

A record of raw strings is adequate.

But auto-generated getter/setter PojoConfigThing.java?

That’s a lie. It masquerades as a domain concept without providing meaning or behavior.


8. The Key Insight: There Is No Legitimate Use Case for Auto-Generated Accessors

This is the part where many articles would compromise — but we won’t.

Objects that consist only of fields and accessors are not objects. They are leaked implementation details. And leaked details force logic into places it should never be.

A domain model requires:

  • invariants

  • constraints

  • intention-revealing methods

  • behavior

None of these are compatible with a model where fields are exposed automatically and mutability is assumed.

So let’s state it plainly:

**There is no object-oriented justification for automatically generating getters and setters.

If a class only has fields, it should not be a class.

If it has behavior, autogenerated accessors destroy that behavior.**

The middle ground doesn’t exist.


9. Lombok Doesn’t Just Generate Code — It Generates Architecture

Every time you add @Getter or @Setter, you are not just removing boilerplate.

You are rearchitecting your system around:

  • freely mutable state

  • uniform field exposure

  • procedural logic in services

  • anemic domain models

  • structural programming disguised as OO

  • loss of intention in APIs

  • duplicated invariants across layers

  • fragile inter-service orchestration

Lombok normalizes these patterns by making them frictionless.

Design is shaped by friction.

When you remove the friction that prevents bad practice, you remove the opportunity for better design.

Lombok gives you short-term cosmetic brevity at the expense of long-term conceptual integrity.


10. “But It Saves Time!” — No, It Doesn’t

The argument that Lombok “saves time” only counts typing time, not thinking time.

Typing time is irrelevant.

Design time is everything.

And Lombok increases design time over the life of a system because:

  • logic gets scattered

  • invariants get duplicated

  • services take on too much responsibility

  • modification becomes unpredictable

  • meaning becomes unclear

  • refactoring becomes dangerous

You do not save time by doing the wrong thing faster.


11. The Alternative: Meaningful Objects, Not Generated Ones

So if we reject Lombok-style property generation, what do we do instead?

We return to fundamentals:

1. Start with behavior, not fields.

Ask: “What does this concept do?”

Not: “What data does it hold?”

2. Create intention-revealing methods.

Instead of getPrice() and getQuantity(), define:

public Money totalCost();
Enter fullscreen mode Exit fullscreen mode

3. Protect invariants where they belong: in the object.

Objects should guard their state.

Not let Services™ police them externally.

4. Reduce mutable state.

Most invariants disappear when objects become immutable or partially immutable.

5. Treat DTOs as boundaries, not models.

Mapping is fine.

Centralizing logic in DTOs is not.

6. Accept that explicit code is not waste — it is clarity.

Expressiveness is the opposite of boilerplate.

Generated code is not expressive.


12. Conclusion: Lombok Isn’t Helping You — It’s Hiding the Real Work

Lombok promises productivity by removing boilerplate.

But boilerplate is not what’s slowing you down.

What slows you down is:

  • unclear domain models

  • procedural service layers

  • mutable data buckets

  • scattered logic

  • accidental complexity

  • duplicated invariants

  • loss of intention

Lombok does not prevent these problems.

It amplifies them — by making it easy to adopt design patterns that kill the business interface of your objects.

The truth is simple but uncomfortable:

Lombok isn’t saving you code.

It’s costing you a design.

And once you realize that getters and setters should not be autogenerated — because they should not exist by default at all — the entire premise of Lombok collapses.

The solution is not more annotations.

The solution is to design objects worth writing code for.

Top comments (0)