DTO, entity, and value object: what's the difference and when to use each
Treating DTO, entity, and value object as synonyms is the root of bad models. Learn what defines each pattern and where most projects go wrong.
In nearly every domain code review I do, I find the same confusion: someone created a class called UserDTO with business logic inside it, or a User entity that gets serialized straight into the API response, or a CPF represented as a loose string wandering through ten different methods. These are three distinct concepts — DTO, entity, and value object — and treating them as synonyms is the root of most of the bad models I see. Each one solves a specific problem, and when you mix them up, you pay the price in coupling, scattered validation, and subtle bugs. This text separates the three precisely and says when to use each.
The focus here is conceptual and practical: what defines each pattern, why they are not interchangeable, and where most projects go wrong.
Entity: the thing that has identity
An entity is an object that has its own identity, continuous over time. The defining trait is not its attributes, it's the identifier. A User with id = 42 is still the same user even if its name, email, and address change. And the reverse is also true: two users with exactly the same name, same email, and same birth date are different people if their ids are different.
That changes how you compare. Entity equality is identity equality, not value equality:
class User:
def __init__(self, id, name, email):
self.id = id
self.name = name
self.email = email
def __eq__(self, other):
return isinstance(other, User) and self.id == other.id
Entities are mutable by nature — they represent something with a lifecycle in the domain. An order is created, paid, shipped, delivered: the same Order, moving through states. Entities live at the heart of the domain and carry behavior, not just data. They are where the business rules that change state belong.
Value object: the thing defined by its values
A value object is the exact opposite when it comes to the equality criterion. It has no identity — it is defined entirely by its values. Money, Address, CPF, date range, geographic coordinate: all of these are value objects. Two instances with the same values are not merely equal, they are the same thing. $10.00 is $10.00, no matter which Money object carries it.
from dataclasses import dataclass
@dataclass(frozen=True)
class Money:
amount: int # cents
currency: str
def __post_init__(self):
if self.amount < 0:
raise ValueError("Money cannot be negative")
Notice two things. First, frozen=True: value objects should be immutable. If you need "another" value, you create a new instance — adding $5.00 to $10.00 returns a new Money of $15.00, it doesn't mutate the original. Immutability eliminates an entire class of aliasing bugs. Second, the validation in the constructor: a value object is the ideal place to encapsulate validation rules. If a negative Money is invalid, it simply cannot exist — the rule lives in one place, not scattered across every call that handles values.
This is my strongest argument in this text: use real value objects. Most projects suffer from "primitive obsession" — CPF as a string, money as a float, email as a string — and the result is validation repeated at every edge of the system, plus the chance of passing an email where a CPF was expected because both are string. A Cpf that only accepts valid values in its constructor makes invalid states unrepresentable. That is worth far more than it seems.
DTO: the thing that only carries data
A DTO (Data Transfer Object) is a bag of data with no behavior, whose only job is to carry information between layers or processes. The canonical example is the JSON that comes in and out of your API. A DTO does not validate business rules, does not compute anything, has no identity — it just carries fields.
@dataclass
class CreateUserRequest: # input DTO
name: str
email: str
@dataclass
class UserResponse: # output DTO
id: int
name: str
The DTO's role is to decouple the external contract from the internal model. The way you expose data in the API doesn't need to be — and shouldn't be — the way you model it in the domain.
Why not expose the entity directly in the API
The temptation is strong: the User entity already has id, name, email, so why not serialize it straight into the response? For two concrete reasons.
The first is coupling. The moment your entity becomes the API contract, any change to the domain becomes a change to the public contract. Renamed a field internally? You broke all the clients. Added a field that shouldn't be public? It leaked to the world.
The second is data leakage. Entities frequently carry things that shouldn't get out: password hashes, internal flags, references to other entities that trigger cascading loads. Serializing the whole entity is like leaving the back door open. The DTO is the deliberate boundary: you choose, field by field, what crosses it.
DTO separate from the entity: the duplication that's worth it
Here comes the objection I always hear: "but UserResponse is almost identical to the User entity, this is just duplication." Yes, at first it looks that way. And even so, keep the DTO separate from the entity.
The reason is that the external contract and the domain model evolve under different forces. The domain changes when the business rule changes. The contract changes when the client needs something different. They will diverge — it's a question of when, not if. The day the API needs to expose a computed field that doesn't exist in the domain, or hide one that does, or flatten a nested structure, you'll be grateful to have the translation layer ready instead of having to tear apart the entity. The "duplication" is decoupling paid up front.
A practical detail that saves you from mistakes: when defining a DTO from a real API JSON, generating the types automatically with a tool like the JSON to TypeScript converter avoids wrong fields and mismatched types between the contract and the code. And if you want to see where DTOs, entities, and value objects fit in the flow of a request, the article on controller, service, and repository shows the layers where each one travels.
Frequently asked questions
Are DTO and value object the same thing?
No, and confusing them is common. Both can be immutable and identity-free, but their purpose differs completely. A value object encapsulates domain rules and validation — a Money that rejects negative values. A DTO is deliberately dumb: it only carries data between layers, validating nothing of the domain. One can even become raw material for the other, but they live in different worlds.
Can I use the same class as both entity and DTO?
Technically yes, in practice avoid it. It works as long as the API contract and the domain model are identical, but the moment they diverge — and they do — you're stuck. Either you pollute the entity with serialization concerns, or you distort the API to follow the domain. Keeping them separate looks like duplication on day one and becomes freedom on day one hundred.
Should every string become a value object?
Not every one, but more than you use today. If a value has a rule (a CPF has a format and a check digit, an email has structure, money has a currency and can't be negative), it deserves a value object. If it's free text with no rule, like a comment, string is enough. The warning sign is repeated validation: if you validate the same format in three places, you're missing a value object.
Does a value object really need to be immutable?
Ideally yes. Immutability is what guarantees that two equal value objects stay interchangeable and that nobody mutates a shared value behind the scenes. Languages support it directly — frozen=True in Python, record in Java/C#, readonly in TypeScript. Mutating a value object defeats the purpose; if you need a different value, create a new instance.
What to take away from this text
The three patterns answer different questions. Entity: "does this have an identity that persists over time?" Value object: "is this defined only by its values and does it carry validation rules?" DTO: "does this just need to cross a boundary with no behavior?" Get the question right and the pattern chooses itself. The two investments that pay off the most: using real value objects to kill primitive obsession and centralize validation, and keeping the DTO separate from the entity even when it looks like silly duplication. The day the contract and the domain diverge — and that day comes — you'll be on the right side of the decision.
- 01 UTF-8 and encoding problems: why accents break and how to fix them What UTF-8 is, code points, mojibake, and why accented text turns to garbage — the mechanism explained end to end.
- 02 MIT, Apache, and GPL Licenses: Practical Differences for Developers MIT, Apache 2.0, and GPL are not the same thing. Understand permissive vs copyleft, patent protection, and compatibility before shipping code to production.