Cloud Parts

Data transfer objects

A plain object that only carries data across a boundary (a network call, an API, a message), shaped for that trip rather than for the business logic behind it.

1 technology on this page ↓

How it works

Martin Fowler’s Patterns of Enterprise Application Architecture defines a Data Transfer Object as “an object that carries data between processes in order to reduce the number of method calls.” A remote call is expensive, so instead of asking for a customer’s name, then the address, then the orders, the client makes one call and gets one object holding all of it. The DTO has fields and nothing else: no business rules, no database access. It must be serializable, and an assembler on the server copies data between it and the domain objects. In today’s web APIs the request and response bodies play this role: CreateOrderRequest and OrderResponse define the contract on the wire, while the Order entity behind them is free to change. That split has a security side. If a framework binds a request straight onto an entity, an attacker can set fields the form never showed, such as isAdmin. A DTO that lists only the fields a caller may edit closes that hole, and it is the natural place to validate input before it reaches the domain. The cost is mapping code between the two shapes, written by hand or generated by libraries such as AutoMapper (.NET) or MapStruct (Java).

Think of it as

A printed order form. The customer fills in only the boxes on the form, and the shop copies them into its own records. There is no box for “discount: 100%”, so however the customer fills it in, they can’t set one. The form is not the shop’s ledger, and changing the ledger doesn’t mean reprinting the form.

Reach for it when

  • Request and response bodies of a public API, so the wire format stays stable while entities and tables change
  • Accepting user input: list only the fields a caller may set, to prevent over-posting
  • A remote interface where one coarse call should replace many small ones
  • A screen or report whose shape differs a lot from the domain model

Think twice when

  • Between layers in one process that share the same shape: Fowler calls such local DTOs harmful because of the mapping work
  • “In case we split into services later”: adding a remote boundary early adds cost now for a maybe
  • As a home for business rules; a DTO carries data, and the behaviour belongs in the domain

A real example

A sign-up form posts userid, password and email. The controller binds the request straight onto the User entity, which also has an isAdmin field. An attacker adds &isAdmin=true to the POST and becomes an administrator. This is the mass assignment (or autobinding, or over-posting) flaw, and OWASP notes that GitHub was hacked this way in 2012, when a user could add his public key to any organization. The fix is a UserRegistrationFormDTO with only the three editable fields: the extra parameter has nowhere to land, and the code copies the three values onto a new User explicitly.

Technologies

Specific products, and what each does differently.

DTO, entity or value object#

Three kinds of object that all hold fields, told apart by what they are for and how they decide equality.

AspectData transfer objectEntityValue object
PurposeCarry data across a boundary in one tripA thing the domain tracks over time, such as an order or a customerA small value such as a point, an amount of money or a date range
Same object whenRarely matters; often compared by value when it is a recordIt has the same identity (a reference or an order number), whatever its fields sayIts fields are equal: two points at (2, 3) are the same point
BehaviourNone beyond serializationBusiness rules and state changesOperations on the value, usually returning a new value
ChangesBuilt, sent, read, thrown awayUsually changes over its lifeBest kept immutable
Seen byBoth sides of the boundary: it is the contractCode inside the domainAnywhere, including inside entities and DTOs
  • Fowler’s DTO pattern page notes that the Sun/Java community first called DTOs “value objects”, which caused confusion with the Value Object pattern, before switching to “Transfer Object”.

Writing and mapping DTOs#

Most of the cost of DTOs is the copying between them and the domain. Languages and libraries attack it from both ends.

Records

Java 16, C# 9

Java records are “transparent carriers for immutable data”, with equals and accessors generated. C# records generate value equality, a readable ToString and copies through with expressions. One line, record OrderResponse(long Id, string Status), is a complete DTO.

Hand-written mapping

explicit

A constructor or a small toResponse() function per type. More typing, but every field that crosses the boundary is visible in code review, which is the point when the boundary is a security one.

AutoMapper

.NET, by convention

An object-to-object mapper: when member names match (FirstName to FirstName) it maps them with almost no configuration, and it can flatten nested objects. Its docs say mapping mostly happens at the boundaries between layers.

MapStruct

Java, at compile time

An annotation processor: you declare a mapper interface and MapStruct generates the implementation during compilation, using plain method calls rather than reflection, so mistakes surface as build errors.

Validate at the boundary, authorise behind it#

Because the DTO is exactly what arrived, it is the place to check shape and ranges before anything touches the domain. ASP.NET Core reads attributes such as [Required] and [Range(0, 999.99)] on the input model, and controllers marked [ApiController] return HTTP 400 with the error details automatically when validation fails. Microsoft’s Web API tutorial lists the reasons for a DTO as preventing over-posting, hiding properties clients shouldn’t see, trimming payload size and flattening nested objects.

A DTO limits which fields a caller can write; it doesn’t decide whether this caller may change this record at all. OWASP’s cheat sheet says so directly: DTOs and field allowlists restrict writable properties but do not replace authorization for the object and operation. It also warns against blocklists such as Spring’s setDisallowedFields("isAdmin"), because a field added later is bindable by default.

When a DTO is just overhead#

Fowler wrote in 2004 that DTOs exist “to shift data in expensive remote calls” and that inside one process they are harmful: a coarse-grained API is harder to use, and every field must be copied between domain objects and DTOs. He quotes Randy Stafford, a contributor to the book, who called the cost of DTOs “significant” and “painful”, perhaps second only to the cost and pain of object-relational mapping.

He allows two local uses: a presentation layer whose shape differs a lot from the domain model, and messages passed between isolated threads. Web APIs fit the first case, since their clients sit across a network and need a stable contract, which is why request and response DTOs remain common even where internal layers pass domain objects directly.

See it in a real stack

Architectures that use data transfer objects

In code · csharp

using System;
using System.Text.Json;

// a request body with one field too many
var json = """{"userId":"bobbytables","email":"bob@example.com","isAdmin":true}""";
var web = JsonSerializerOptions.Web;

// bound straight to the entity: the attacker becomes an admin
var bound = JsonSerializer.Deserialize<User>(json, web)!;
Console.WriteLine($"entity: admin={bound.IsAdmin}");          // entity: admin=True

// bound to a DTO: there is no IsAdmin to set, so it is ignored
var dto = JsonSerializer.Deserialize<SignupRequest>(json, web)!;
Console.WriteLine(dto);  // SignupRequest { UserId = bobbytables, Email = bob@example.com }

var user = new User { UserId = dto.UserId, Email = dto.Email };  // explicit mapping
Console.WriteLine($"dto: admin={user.IsAdmin}");               // dto: admin=False

// the DTO: only the fields a caller may set
record SignupRequest(string UserId, string Email);

// the entity: also holds fields a caller must never set
class User
{
    public string UserId { get; set; } = "";
    public string Email { get; set; } = "";
    public bool IsAdmin { get; set; }
}

Often confused with

Where it came from2002–2021

The pattern comes from the early days of distributed Java. In Enterprise JavaBeans every call to a bean was potentially a remote call with network overhead, and a client that needed several of a bean’s attributes made a remote call for each one. Sun’s Core J2EE Patterns answered with a single object that the bean filled with all its values and passed by value to the client, first called a Value Object and later a Transfer Object.

Martin Fowler’s Patterns of Enterprise Application Architecture (2002) gave it the name Data Transfer Object, with an assembler on the server copying between DTOs and domain objects. Because the Java world’s “value object” clashed with the Value Object pattern (a small object compared by its values), the Java community switched to “Transfer Object”. In 2004 Fowler wrote a follow-up warning against “local DTOs” used where no remote call exists, and in 2013 added that DTOs also keep serialization code out of the rest of the program.

Web APIs gave DTOs a second job: security. Frameworks that bind request parameters onto objects let attackers set fields the form never offered. OWASP’s mass assignment cheat sheet cites GitHub’s 2012 hack as its case study and recommends dedicated input objects with only the editable fields; Microsoft’s ASP.NET tutorials teach the same DTOs to prevent over-posting. Languages then made DTOs cheap to write: C# 9 added records in 2020, and Java 16 finalised records in 2021.

Why it’s built this way

One coarse object instead of many fine-grained calls
Each remote call costs a network round trip. Batching every value a client needs into one object turns several calls into one.
Data only, with an assembler doing the mapping
Keeping behaviour out of the DTO and conversion in a separate assembler leaves domain objects free of wire formats, and gives one place to change serialization.
A new name instead of “value object”
The Sun/Java community’s original name collided with the Value Object pattern and caused confusion, so it became “Transfer Object”, and Fowler used “Data Transfer Object”.
Only the fields a caller may set
Automatic request binding writes whatever fields arrive. A dedicated input object with an allowlist of fields is the defence OWASP recommends against mass assignment.

Timeline

  1. 2002

    Fowler’s Patterns of Enterprise Application Architecture names the Data Transfer Object

  2. 2004

    Fowler’s “Local DTO” warns against DTOs without a remote boundary

  3. 2012

    GitHub is hacked through mass assignment

  4. 2013

    Fowler adds that DTOs also encapsulate serialization

  5. 2020

    C# 9 adds records

  6. 2021

    Java 16 finalises records (JEP 395)

Sources12

Improve this page on GitHub