I recently revisited one of my old backend projects.
Everything was working fine. No errors. No failing APIs.
But while reading the code, one design decision immediately stood out — I had exposed persistence entities directly in API requests and responses.
At the time, it felt like a smart move. Looking back now, I realize it was a shortcut with hidden costs.
The Shortcut That Seemed Like a Smart Move
In the initial version, my controller looked something like this:
(Here, BookItem is a persistence entity class used for creating book records.)
@PostMapping("/books")
public BookItem create(@RequestBody BookItem bookItem) {
return bookService.save(bookItem);
}
Why did this feel right?
- The entity already had all the fields
- No extra classes to write
- Faster development
DTOs felt like unnecessary boilerplate. For a small project, this approach worked — and that’s exactly why it was misleading.
The First Problem: Clients Can Send Too Much
When an entity is used directly as a request body, every field becomes writable.
This means a client can:
- Set database IDs manually
- Modify fields they should never control
- Send values meant only for internal logic
The framework doesn’t know intent — it simply maps fields. Without a boundary, the API trusts the client far too much.
The Second Problem: Leaking Sensitive Data
Entities are designed for persistence, not for exposure.
When returned directly in API responses, they often include:
- Internal flags
- Audit timestamps
- Fields irrelevant or unsafe for clients
Once clients start consuming these fields, they become part of the public contract — even if you never intended them to be.
The Real Cost: Change Becomes Dangerous ‼️
This is the most critical problem — and the one that usually appears too late.
A Realistic Example
Initial Entity
@Entity
public class BookItem {
@Id
private String id;
private String title;
private String author;
private int pages;
}
API Response
{
"id": "123",
"title": "Clean Code",
"author": "Robert Martin",
"pages": 464
}
Clients build their logic around this response.
Later: A Normal Business Change
You decide that pages is inaccurate and replace it with wordCount.
@Entity
public class BookItem {
@Id
private String id;
private String title;
private String author;
private int wordCount;
}
You didn’t touch the controller. You didn’t change the API intentionally.
But the response now becomes:
{
"id": "123",
"title": "Clean Code",
"author": "Robert Martin",
"wordCount": 150000
}
What Just Happened?
-
pagesdisappeared -
wordCountappeared - Clients break
- Frontend crashes
- Mobile apps fail
This happened because the entity was acting as the API.
Every internal refactor became a breaking change.
That is why this design is dangerous.
Why DTOs Solve This Problem
DTOs create a stable boundary between internal models and external consumers.
With DTOs:
- Clients can send only allowed fields
- Responses expose only safe data
- Entities can change freely
- APIs remain stable
Final Thought
Using entities directly in APIs often feels productive at first.
But the real cost appears later — when requirements change and refactoring becomes risky.
An entity is not an API.
DTOs may add a few extra classes, but they protect your system from silent breakage and future pain.




Top comments (0)