Skip to content

data transfer object (DTO)

A data transfer object, or DTO, is a plain object that carries data across a boundary, such as between a server and its clients or between two layers of an application, without any business logic of its own. It bundles the fields a receiver needs into one package that’s easy to serialize and send.

Martin Fowler’s catalog of enterprise application patterns lists the DTO as a design pattern and defines it as “an object that carries data between processes in order to reduce the number of method calls.” Instead of making one slow network call per field, a client asks once and gets everything back in a single DTO.

Say a web API returns a user’s profile. The domain object also holds a password hash that no client should ever see. So the endpoint copies only the public fields into a DTO built as a data class and serializes that instead:

Language: Python
>>> import json
>>> from dataclasses import asdict, dataclass

>>> @dataclass
... class User:
...     user_id: int
...     name: str
...     email: str
...     password_hash: str
...
>>> @dataclass(frozen=True)
... class UserDTO:
...     user_id: int
...     name: str
...     email: str
...
>>> def to_dto(user):
...     return UserDTO(user.user_id, user.name, user.email)
...
>>> user = User(42, "Ada", "ada@example.com", "5f4dcc3b5aa765d6")
>>> json.dumps(asdict(to_dto(user)))
'{"user_id": 42, "name": "Ada", "email": "ada@example.com"}'

The to_dto() function plays the role Fowler calls an assembler. It maps the domain object onto the DTO, so the password hash never reaches the wire. The DTO itself stays a passive bundle of fields, and freezing it signals that the receiver reads the data rather than changes it. Here’s how the pieces fit together across the process boundary:

A to_dto() assembler copies a User object into a UserDTO sent as JSON to the client, dropping password_hash on the server.
A DTO Carries Only Public Fields Across the Boundary

How It Shows Up in Practice

A Python developer most often meets DTOs at the edges of a web service. Pydantic models used as FastAPI request and response models fill this role, as do Django REST Framework serializers. Each one declares exactly which fields come in with a request and which go out in a response.

DTOs also carry data between microservices, onto message queues, and out of a data layer built on object-relational mapping. Because callers depend on a DTO’s shape, that shape becomes part of the service’s contract. Renaming a field is a breaking change that other teams have to plan for.

Common Pitfalls

A common mistake is skipping the DTO and returning a database model straight to the client. Every new column then leaks into the response by default, including sensitive ones like the password hash above.

Overusing DTOs causes the opposite problem. Fowler notes that inside a single process they’re usually harmful, because every domain class gains a near-identical twin plus mapping code to keep in sync. He makes exceptions for a presentation layer whose shape differs sharply from the domain model, and for messages passed between threads.

A DTO that picks up validation rules, calculations, or database calls has also drifted from the pattern. Keeping it to plain data is what makes it cheap to serialize and safe to hand to code that doesn’t share the rest of the application’s classes.

Serialize Your Data With Python

Tutorial

Serialize Your Data With Python

In this in-depth tutorial, you'll explore the world of data serialization in Python. You'll compare and use different data serialization formats, serialize Python objects and executable code, and handle HTTP message payloads.

intermediate data-science web-dev

For additional information on related topics, take a look at the following resources:

Have a question about this? Mentor AI can show you examples, compare related terms, and point you to tutorials.


By Martin Breuss • Updated Oct. 27, 2026