Implement DTOs in Java with boundary-specific fields, simple records or classes, and explicit mapping from domain or persistence models.
DTO (Data Transfer Object): A simple object whose job is to carry data across a boundary without bringing business behavior or internal model details with it.
In Java, a DTO is useful when the shape required by an API, message, or remote call is not the same shape the domain model should expose internally. That is the core reason to implement one. Not “because layers need their own classes” by default.
The first implementation decision is not whether the DTO should be a class or record. It is what boundary the DTO belongs to:
Once that boundary is clear, the field set becomes much easier to justify.
For many Java DTOs, a record is a strong default:
1public record CustomerSummaryDto(
2 String id,
3 String displayName,
4 String tier,
5 boolean active
6) {}
This works well because DTOs are usually:
Records are not mandatory, but they often match the purpose of a DTO very well.
Suppose the domain model contains:
The DTO does not need to mirror all of that.
1public final class Customer {
2 private final CustomerId id;
3 private final FullName name;
4 private final LoyaltyTier tier;
5 private final CreditProfile creditProfile;
6 private final AuditMetadata auditMetadata;
7
8 // domain behavior omitted
9}
10
11public record CustomerSummaryDto(
12 String id,
13 String displayName,
14 String tier
15) {}
That difference is the point. The DTO carries the boundary-relevant view of the data, not the full internal model.
Even when mapping is later automated, the boundary rule should be understandable from explicit code:
1public final class CustomerDtoMapper {
2 public CustomerSummaryDto toSummaryDto(Customer customer) {
3 return new CustomerSummaryDto(
4 customer.id().value(),
5 customer.name().formatted(),
6 customer.tier().name()
7 );
8 }
9}
This makes three important things visible:
DTOs become weak when they turn into one of these:
If the DTO is just a copy of the entity plus public setters, the design probably needs another pass.
DTOs are often serialized to JSON, XML, or message formats, but serialization is not the pattern’s reason to exist. The real purpose is owning a boundary shape deliberately. Serialization is just one consequence.
When reviewing a Java DTO, ask:
DTO is strong when it clarifies the boundary contract. It is weak when it exists only because “layered architecture” was copied from an old diagram.