DTOとは「Data Transfer Object」の略で、レイヤー間でデータを受け渡す専用オブジェクトです。
DTOは「箱」、Entityは「器」、DAOは「係」と覚えると迷いません。
「Entityをそのまま返せば楽では?」と思うかもしれませんが、それがパスワードハッシュの漏洩やDBカラム変更の連鎖事故につながります。
前提はJava 21/25 LTS・Spring Boot 4.x。比較表とRecord型の実装、アンチパターン4選まで紹介します。
この記事でわかること
- JavaのDTOとは何か、なぜ必要なのか
- DTO・Entity・DAOの違い(VO・Form・POJOとの使い分けも)
- Spring Bootでの実装方法(サンプルコード付き)
- Java 17以降のRecord型を使った現代的なDTOの書き方
- 現場でやりがちなDTOアンチパターン4選と回避策
- DTOを導入すべきか迷ったときの判断フロー
私も新人時代は「DTOは冗長だ」と感じていました。しかしある改修でDBの変更が画面表示まで波及し、1日の作業が3日に膨らむ事故を起こしました。
層を分離する重要性はここで腑に落ちました。
PARTS LIST /目次
JavaのDTOとは?意味と役割をわかりやすく解説
JavaにおけるDTOは、異なるシステムやレイヤー(層)の間でデータを転送するための専用オブジェクトです。
たとえば、ユーザーの情報を扱うWebアプリケーションを考えてみましょう。
- ユーザーがブラウザから入力した情報(リクエスト)を、Controllerが受け取る
- Controllerは、受け取ったデータをServiceに渡してビジネスロジックを実行する
- Serviceは、必要に応じてRepositoryを使い、データベース(Entity)を操作する
- 処理結果を、逆の順序でブラウザ(レスポンス)に返す
この一連の流れの中で、各層の間をデータが行き来します。このデータの受け渡しに使われる「運び屋」がDTOの役割です。
DTOとEntityがそれぞれどの層の間を流れるかを図にすると、次のとおりです。
UserCreateRequestDto@RequestBody で JSON が入る。name・email・password(平文)の3項目だけuserService.createUser(requestDto)User に詰め替えるパスワードはハッシュ化してからセット。id はまだ未採番User が戻るid・name・email に加え、パスワードハッシュも持っているconvertToDto() で UserResponseDto にpassword の項目はない。ブラウザに届くのは id・name・email だけDTO(Data Transfer Object)の基本概念
DTOは、データを入れるフィールドと、それを読み書きするgetter/setterメソッドだけを持つクラスです。このように特定のフレームワークに縛られないシンプルなJavaオブジェクトをPOJOと呼びます。
ビジネスロジックのような複雑な処理は含みません。
役割を絞るほど各層の責任がはっきりし、システムの構造もシンプルに保てます。
JavaでDTOがよく使われる理由|密結合を防ぐ設計思想
JavaでDTOが頻繁に使われる最大の理由は、システムの関心事を分離し、各層の独立性を高めるためです。
もしDTOを使わずに、データベースと直接やりとりする「Entity」をそのまま画面表示にまで使い回すとどうなるでしょうか。
UIの都合で表示項目を少し変えたいだけなのに、データベースの構造にまで影響が及びます。
逆に、データベースの項目名を変更しただけで、APIのレスポンス形式が変わり、フロントエンドで大規模な修正が必要になる場合もあります。
各層が密接に結びつくと、一方の変更がもう一方に影響する「密結合」な状態に陥ります。
DTOは、この密結合を避け、各層を疎結合に保つための重要な緩衝材として機能するのです。
DTO・Entity・DAOの違いを比較表で整理
Java初学者が最も混乱しやすいのが、DTO・Entity・DAOの違いです。DTOとEntityはどちらもデータを持つクラスですが、目的が違います。DAOはデータを持たず、DBを読み書きする「処理」側のクラスです。
Entityは「永続化層」の住人で、データベースのテーブル構造と密接に関わります。
JPA(Jakarta Persistence。旧称 Java Persistence API)では@Entity アノテーションを付けて、テーブルのレコードを表現します。
パスワードのような、外部に公開すべきではない機密情報も保持することがあります。
一方、DTOは「プレゼンテーション層」や「アプリケーション層」の住人です。
画面に表示したい項目だけ、あるいはAPIで受け取りたい項目だけを過不足なく定義します。
Entityの情報をそのまま使うのではなく、必要なデータだけをDTOに詰め替えてから外部とやり取りするのです。
この使い分けにより、データベースの構造を外部から隠蔽し、セキュリティを高める効果もあります。
3つの関係は、DAOがEntityをDBから取得し、ServiceがEntityをDTOに詰め替え、ControllerがDTOを返すという一方通行の流れで整理できます。DAOとの違いは、後半の「DTOとDAO(Repository)の違い」で詳しく解説します。
JavaでDTOを使うメリット・デメリット
DTOは事故を防ぐ一方で、クラスが増えるという代償もあります。両面を先に押さえておきましょう。
DTOを導入することで得られるメリット
DTOを適切に利用すると、主に以下のメリットが得られます。
- DTOを使えば、APIのレスポンスに必要な情報(例: ユーザー名、メールアドレス)だけを抜き出して返せる。意図しない情報漏洩を防ぎ、セキュリティを高められる
- フロントエンドとバックエンドの独立性が高まり、それぞれが並行して開発を進めやすくなる
DTOを使うときの注意点・デメリット
最大のデメリットは、コード量が増加することです。
Entityに加えてDTOクラスを定義し、さらにEntityとDTOの間でデータを相互に変換する「Mapper」と呼ばれるロジックを実装します。
単純なアプリケーションでは、この手間が開発速度を落とす原因です。
また、クラスの数が増えることで、管理が煩雑になる側面もあります。
特に、似たような構造のDTOが乱立すると、どれを使えばよいのかわからなくなり、かえって生産性を下げるリスクがあります。
どんなプロジェクトにDTOが向いているか
前述のメリット・デメリットを踏まえると、DTOの導入が特に効果的なのは、以下のようなプロジェクトです。
- 中規模から大規模のWebアプリケーション: 複数の開発者が関わり、機能が複雑になるプロジェクトでは、関心の分離による保守性の向上が大きなメリットになる
- マイクロサービスアーキテクチャ: サービス間で通信する際は、APIの仕様をDTOで明確に定義する必要がある
- フロントエンドとバックエンドが分離されているプロジェクト: ReactやVue.jsと連携する場合、DTOを介してJSON形式でやりとりするのが一般的
- セキュリティ要件が厳しいシステム: 個人情報などを扱うシステムでは、情報漏洩リスクを低減するためにDTOの利用が強く推奨される
逆に、個人開発の小さなツールや、ごく単純なCRUD機能しかない管理画面では、DTOを導入するメリットよりもコードが増えるデメリットのほうが大きくなります。
DTOを導入すべきか迷ったときの判断チェックリストを用意しました。1つでも該当すれば導入を強く推奨します。
- 外部(フロントエンド・他システム)にAPIを公開する
- 認証情報・個人情報・決済情報などの機密データを扱う
- 2名以上のチーム開発である
- 将来的にテーブル構造の変更が予想される
すべてNoで「個人開発の使い捨てツール」なら、Entityを直接返しても大きな問題にはなりません。
JavaでDTOを実装する方法【サンプルコード付き】
基本的なDTOクラスの書き方
DTOクラスは、データを保持するためのフィールドと、それに対応するgetter/setterを持つシンプルなPOJOです。
たとえば、ユーザー情報を画面に表示するためのDTOは以下のとおりです。
public class UserDto {
private Long id;
private String name;
private String email;
// GetterとSetter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
毎回getter/setterを書くのは手間なので、Lombokというライブラリを導入すると、アノテーションを付けるだけで自動生成できます。
import lombok.Data;
@Data // Getter, Setter, toString, equals, hashCodeを自動生成
public class UserDto {
private Long id;
private String name;
private String email;
}
Lombokを使うことで、コードがすっきりします。
Spring BootでDTOを使う例
Spring Bootを使ったWebアプリケーションで、DTOがどのように使われるかを見てみましょう。
ここでは、新しいユーザーを作成するAPIを例にします。
まず、クライアントから送られてくるリクエストデータを受け取るためのDTO(Request DTO)を定義します。
UserCreateRequestDto.javaimport lombok.Data;
@Data
public class UserCreateRequestDto {
private String name;
private String email;
private String password;
}
次に、このDTOをリクエストボディとして受け取るControllerを実装します。
UserController.java@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
// UserServiceをコンストラクタインジェクション
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public UserResponseDto createUser(@RequestBody UserCreateRequestDto requestDto) {
// Service層にDTOを渡してユーザー作成処理を依頼
return userService.createUser(requestDto);
}
}
@RequestBody を付けると、SpringがJSON形式のリクエストデータを UserCreateRequestDto に自動でマッピングします。
Controller・Service・Entityとの連携例
Controllerが受け取ったDTOは、Service層に渡されてビジネスロジックが実行されます。
Service層では、DTOからEntityへの変換が行われ、データベースに保存されます。
User.java (Entity)import lombok.Data;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private String password; // 実際はハッシュ化されたパスワードを保存
}
UserService.java
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public UserResponseDto createUser(UserCreateRequestDto requestDto) {
// DTOからEntityへの変換
User user = new User();
user.setName(requestDto.getName());
user.setEmail(requestDto.getEmail());
// パスワードはハッシュ化するなどの処理を行う
user.setPassword(hashPassword(requestDto.getPassword()));
// Repositoryを使ってデータベースに保存
User savedUser = userRepository.save(user);
// 保存したEntityをResponse DTOに変換して返す
return convertToDto(savedUser);
}
// EntityからDTOへの変換メソッド
private UserResponseDto convertToDto(User user) {
UserResponseDto dto = new UserResponseDto();
dto.setId(user.getId());
dto.setName(user.getName());
dto.setEmail(user.getEmail());
return dto;
}
// パスワードハッシュ化のダミーメソッド
private String hashPassword(String password) {
// Spring SecurityのPasswordEncoderなどを使う
return "hashed_" + password;
}
}
UserResponseDto.java
import lombok.Data;
@Data
public class UserResponseDto {
private Long id;
private String name;
private String email;
}
この例では、以下の流れで処理が進みます。
- Controllerが
UserCreateRequestDtoを受け取る。 - Serviceが
UserCreateRequestDtoを受け取り、UserEntityに変換する。 - 変換した
UserEntityをデータベースに保存する。 - 保存された
UserEntityを、レスポンス用のUserResponseDtoに変換する。 - Controllerが
UserResponseDtoをクライアントに返す。
注目すべきは、レスポンス用の UserResponseDto にはパスワードが含まれていない点です。
Java 21/25のRecord型で書くDTO(現代的な書き方)
Java 14でプレビュー導入され、Java 16で正式化(JEP 395)されたRecord型は、DTOの記述量を大幅に減らせる機能です。
Record型はJava 21/25 LTSで標準的に使えます。Spring Boot 4.x 以降のプロジェクトでは、LombokよりもRecord型を第一候補にするケースが増えています。
Record型を使ったDTOの書き方
前述の UserResponseDto をRecord型で書き直すと、以下の1行で済みます。
public record UserResponseDto(Long id, String name, String email) {}
Record型では、コンパイラが以下を自動生成します。
- プライベートなfinalフィールド
- フィールド名と同じ名前のアクセサメソッド(
getId()ではなくid()) - 全フィールドを引数に取るコンストラクタ
equals()、hashCode()、toString()メソッド
Record型のメリットと注意点
Record型を使う最大のメリットは、不変(Immutable)なDTOをシンプルに定義できることです。
setterが存在しないため、値が意図せず書き換えられるバグを防げます。フィールドがfinalなので、中身(構成要素)も不変であればスレッド間で安全に共有でき、APIレスポンスとの相性も良好です。
私の現場では、Lombokの@DataクラスをRecord型に置き換えました。するとequals()/hashCode()を手動テストしていたユニットテストが不要になり、DTO関連のテストコード行数が実測で約3割減りました。
Record型はコンパイラが等価性判定を保証してくれます。そのため「フィールドを追加したのにequalsの更新を忘れて比較テストが通らない」という事故が起きなくなったのが、実務上の一番の恩恵です。
一方、以下の点には注意が必要です。
- Bean Validationは効く:
@NotNullや@Sizeはコンポーネント引数に付与でき、Spring Bootでも問題なく動作する - 継承ができない: Record型は暗黙的にfinalなので、共通親クラスを継承する設計には使えない
- JPA Entityには向かない: Entity は引数なしコンストラクタと可変フィールドが必要なため、Record型は避けてclass + Lombokにする
- Jacksonでのデシリアライズは自動対応: Jackson 2.12+ は Record型を追加設定なしでデシリアライズできる
新規プロジェクトでは、まずRecord型で書けないかを考えます。可変性やデフォルト値が必要な場合だけLombokの @Data を使う、という使い分けが2026年8月時点の主流です。
また、Java 21 LTSで正式化されたRecord Patterns(JEP 440)も使えます。if (obj instanceof UserResponseDto(Long id, String name, String email)) と書くと、Record型DTOをフィールドに分解しながら変数へ束縛できます。
読み取りコードの記述量がさらに削減できるため、Java 21以降のプロジェクトでは積極的に活用を検討しましょう。
Record型とLombokの使い分けフロー
実務で迷ったときの判断基準を整理しておきます。
- 不変でOK・DBマッピング不要(レスポンスDTOの大半) → Record型を第一候補
- 途中で値加工が必要・Builderパターンで組み立てたい → Lombokの
@Dataや@Builder - JPA Entity(引数なしコンストラクタと可変フィールドが必要) → class + Lombok(Record型は使えない)
私のチームでは、Spring Boot 3.2から4.1へのアップグレード時にレスポンスDTOをすべてRecord型に移行しました。
setterを廃止すると、レビューで「このフィールド、途中で書き換えられていないか?」を確認する手間がなくなりました。コードレビューの平均所要時間も、体感で2割ほど短縮されています。
ただし、リクエストDTOの一部はあえてRecord型に移行しませんでした。
バリデーションエラー時にフィールドごとの補足情報を後付けで詰め直す処理があり、Record型のfinalフィールドではその実装が回りくどくなったためです。
私なりの線引きは「新規はRecord型、既存の可変ロジックが絡む箇所は無理に移行しない」です。Record型を導入する現場なら、一度は検討する価値があると考えています。
DTOとEntity・DAO・VO・Form・POJOの違いを比較
DTOと似た目的で使われる言葉や、関連する概念がいくつかあります。
DTOとDAO(Repository)の違い|役割は別物
DAOは「Data Access Object」の略で、データベースへのアクセス処理を担当する層です。
名前が似ているためDTOと混同されがちですが、役割は全く異なります。
端的に言うと、DAOは「データを取りに行く人」、DTOは「データを運ぶ箱」です。
DAOがSELECTやINSERTなどのSQLを発行してデータベースから結果を取得します。
その結果をEntityやDTOに詰めて、呼び出し元に返します。
Spring Bootの世界では、DAOと近い役割を担うのがRepositoryクラスです。
つまり、DAO・Entity・DTOは競合する概念ではなく、役割分担している協力関係にあります。
VO(Value Object)との違い
VO(Value Object)もデータを保持するオブジェクトですが、DTOとは異なる特性を持ちます。
VOは「値そのもの」を表現するオブジェクトです。
VOの大きな特徴は、不変(Immutable)であることと、等価性の判断をIDではなく保持している値で行うことです。
具体例として、「金額」を表現する Money クラスを考えます。「1,000円」という値は、誰が持っていても「1,000円」です。
オブジェクトが作られた後に金額が変わることはありません(不変)。
そして、2つのMoneyオブジェクトが同じ「1,000円」という値を持っていれば、それらは等しいと判断されます。
一方、DTOは値そのものを表すのではなく、転送したいデータをまとめる入れ物です。getter/setter型のDTOは可変(Mutable)ですが、Record型を使えば不変にもできます。
UserDto は、同じ名前やメールアドレスを持っていても、IDが違えば別のユーザーのデータです。
DTOとForm(フォームオブジェクト)の違い
Spring MVCでは、画面の入力フォームから送信されたデータを受け取るための「Formオブジェクト」を使うことがあります。
FormもDTOも「データの入れ物」という点では似ていますが、データの流れる方向と用途が異なります。
REST APIが主流の現在では、Formオブジェクトを使わずにリクエストDTOで直接受け取るケースが増えています。
Spring MVCのテンプレートエンジン(Thymeleaf等)を使う場合は、Formを分ける意味があります。一方、JSON APIのみの構成ならリクエストDTOに統一しても問題ありません。
POJO(Plain Old Java Object)との関係
POJOは「Plain Old Java Object」の略で、直訳すると「古き良き普通のJavaオブジェクト」となります。
これは、特定のフレームワークやライブラリが要求する特別なルールに縛られない、Javaの基本的な仕様だけで作られたオブジェクトを指します。
extends で特定のクラスを継承したり、implements で特定のインターフェースを実装したりする制約がなく、自由に定義できるのが特徴です。
DTOは、このPOJOの一種です。 上記で作成した UserDto も、特定のフレームワークへの依存がないため、POJOです。
同様に、EntityやVOも多くの場合POJOとして実装されます。
POJOは広義な言葉で、DTOはその具体的な使い方の1つだと理解しておくとよいでしょう。
DTOを使うときのベストプラクティス
最後に、DTOを効果的に活用し、メンテナンスしやすいコードを書くためのベストプラクティスをいくつか紹介します。
命名規則とパッケージ構成の考え方
DTOクラスは、その役割がひと目でわかるように命名してください。
- リクエスト用DTO:
UserCreateRequest,ProductUpdateRequestのようにRequestやFormを接尾辞につける - レスポンス用DTO:
UserResponse,ProductDetailResponseのようにResponseやDtoを接尾辞につける
加えて、DTOクラスは専用のパッケージにまとめて管理するのが一般的です。
com.example.myapp
├── controller
│ └── UserController.java
├── service
│ └── UserService.java
├── repository
│ └── UserRepository.java
├── entity
│ └── User.java
└── dto
├── UserCreateRequest.java
└── UserResponse.java
このようなパッケージ構成にすることで、プロジェクトの全体像が把握しやすくなります。
ModelMapperやMapStructを使った変換効率化
前述のとおり、手動でのマッピングはコードの肥大化とバグの温床です。
中規模以上のプロジェクトでは、ModelMapperやMapStructのようなマッピングライブラリの導入を積極的に検討しましょう。
- ModelMapper: 設定が簡単で手軽に導入できる。リフレクションを利用するため、実行速度はMapStructに劣る場合がある
- MapStruct: コンパイル時にマッピングコードを自動生成するため、高速に動作する。アノテーションベースで設定が少し複雑だが、パフォーマンスが求められる場合に強力な選択肢となる
プロジェクトの要件に合わせて適切なライブラリを選定し、変換ロジックをシンプルに保ってください。
ModelMapperを使った例:ModelMapper modelMapper = new ModelMapper();
// DTOからEntityへ
User user = modelMapper.map(requestDto, User.class);
// EntityからDTOへ
UserResponseDto responseDto = modelMapper.map(savedUser, UserResponseDto.class);
手動でフィールドを一つずつセットするコードが不要になり、生産性が大幅に向上します。
私の経験では、ModelMapperからMapStructに切り替えると、フィールド名の不一致をコンパイル時にエラーとして検出できるようになりました。その結果、「DTOにフィールドを追加したのにマッピング漏れで画面にnullが表示される」というバグが激減しています。
初期の学習コストはやや高いものの、中長期で見ればMapStructのほうが保守コストは低くなります。
導入初期に苦労したのは、チームの新人メンバーが「生成されたマッパー実装クラスがどこにあるかわからない」と混乱したことです。
MapStructはアノテーションプロセッサでビルド時にコードを自動生成するため、ソースコード上には実装が見当たりません。
target/generated-sources(Mavenの場合)配下を見る習慣を最初にオンボーディング資料へ明記しておくと、この種のつまずきを防げます。
メンテナンスしやすいDTO設計のポイント
長期的にメンテナンスしやすいDTOを設計するためには、以下の点を意識するとよいでしょう。
- 必要なデータだけを持つ: DTOには、そのユースケースで本当に必要なフィールドだけを定義する。使わないデータをとりあえず入れておく、という設計は避ける
- バリデーションを活用する: リクエストDTOに
@NotNullや@SizeなどのBean Validationアノテーションを付けると、Controllerに届く前に不正なデータを弾ける。これにより、Service層のロジックをシンプルに保てる - DTOのネストを適切に利用する: 関連する情報が複数ある場合、DTOの中に別のDTOを持たせる(ネストする)ことで、複雑なデータ構造を表現できる。ただし、ネストが深くなりすぎると逆に扱いにくくなるため、バランスが重要
DTO設計でよくあるアンチパターンと回避策
DTOは使い方を誤ると、むしろコードを複雑にし、バグを生みます。私が実プロジェクトで見かけた代表的なアンチパターンを4つ紹介します。
アンチパターン1: EntityをDTO代わりに使う
「手間だから」という理由でEntityをそのままControllerから返してしまうパターンです。
APIレスポンスにパスワードハッシュなどの機密情報が漏れたり、データベースのカラム追加がフロント側に波及したりと、事故の元です。
私が参画したプロジェクトでは、Entityをそのまま返していたAPIがありました。ある日、「管理画面から見えてはいけない退会済みユーザーのメールアドレス」がフロントエンドに露出していることが発覚しました。
DTOで公開フィールドを明示的に選別していれば防げた事故です。
回避策: APIの境界には必ず専用DTOを定義し、EntityからDTOへの詰め替え処理をService層で実施します。
アンチパターン2: 巨大な汎用DTOの使い回し
1つの UserDto に30フィールドを詰め込み、登録・更新・一覧・詳細すべてで使い回すパターンです。
これにより未使用フィールドが大量に発生し、nullチェックや意図しない情報漏洩を招きます。
私の現場では、肥大化UserDtoのレビュー時に使用フィールドの判定だけで平均10分以上かかり、PRが渋滞しました。
回避策: ユースケース単位でDTOを分割します。たとえば UserCreateRequest、UserUpdateRequest、UserListResponse、UserDetailResponse のように分けます。
クラス数は増えますが、各APIの契約が明確になります。
アンチパターン3: DTOにビジネスロジックを書く
DTO内に金額計算や認可判定などのロジックを書いてしまうと、Service層とDTOの責務が混ざり、テストもしづらくなります。
回避策: DTOは「データの入れ物」に徹します。
ロジックが必要な表示値は、Service層でドメインオブジェクトから計算し、DTOには計算結果だけを詰めます。
アンチパターン4: setter公開で可変DTOを多用する
可変DTOが危ないのは、渡した先が同じ箱を書き換えられるからです。図で動かすJVMメモリ|スタックとヒープ・参照・GCの関係では、引数として渡されるのが「値のコピー」なのか「場所のコピー」なのかを、矢印で見ながら確認できます。
レスポンスDTOなのにsetterを公開していると、途中の処理でうっかり値を書き換えてしまい、バグの再現が難しくなります。
回避策: レスポンス用DTOはRecord型で不変にします。
リクエスト用DTOも、基本はコンストラクタで値を受け取り、setterは避ける方針が安全です。
JavaのDTOに関するよくある質問(FAQ)
Q. DTO・Entity・DAOの違いを一言で言うと?
DTOは層の間でデータを運ぶ箱、EntityはDBのテーブルを表すクラス、DAOはDBを読み書きする処理クラスです。DAOが取得したEntityを、ServiceがDTOに詰め替えて画面やAPIに返します。
Q. 小規模プロジェクトでもDTOは必須ですか?
個人開発の小さなCRUDアプリなら、必須ではありません。ただし、APIを公開する、認証情報を扱う、複数人で開発するのいずれかに当てはまる場合はDTOを導入する価値があります。後からDTOを差し込むほうがコストが高いので、早めの導入が安全です。
Q. DTOとRecord型、どちらを使えばいいですか?
Java 17以降のプロジェクトなら、レスポンスDTOはRecord型が第一候補です。Jackson連携も自動で動き、不変性でバグを防げます。リクエストDTOも基本はRecord型で問題ありませんが、バリデーション後に値を加工する必要があるならLombokの @Data や @Builder を検討してください。
Q. DTOのネストは深くしてもいいですか?
ネストは2階層までが実務上の目安です。3階層以上になると、JSON構造が読みにくくなり、マッピングコードも複雑化します。深くなりそうなときは、APIを分割するか、フラットなDTOに組み替えるのが無難です。
Q. EntityとDTOの変換が面倒です。楽にできませんか?
MapStructの利用が最も効率的です。コンパイル時にマッピングコードを生成するためリフレクションに頼らず、実行速度が高速で、型チェックも効きます。2026年8月時点のSpring Bootプロジェクトでは、ModelMapperよりもMapStruct(安定版1.6系。1.7系はベータ提供中)を選ぶ現場が増えています。
Q. DTOとFormオブジェクトは同じものですか?
厳密には異なります。Formは画面入力を受け取る専用オブジェクトで、バリデーションが主な役割です。DTOはレイヤー間のデータ転送全般に使います。ただし、REST APIのみの構成ではリクエストDTOがFormの役割を兼ねるため、実質的に同じものとして扱うプロジェクトも多いです。
Q. DTOの読み方は?Java以外の言語でも使いますか?
DTOの読み方は「ディーティーオー」で、Data Transfer Object(データ・トランスファー・オブジェクト)の頭文字です。一般的にはアルファベットをそのまま読みます。
メモ
DTOはJavaに限らず、C#(.NET)・PHP(Laravel)・TypeScript(NestJS)など多くの言語で「レイヤー間のデータ転送専用オブジェクト」として同じ考え方が使われている。
本記事のコード例はJava/Spring Boot前提ですが、Entityを直接返さずDTOに詰め替えるという原則はどの言語でもそのまま通用します。
まとめ|DTOを理解すると設計の質が上がる
冒頭の「EntityをそのままAPIで返せば楽では?」への答えは、「楽なのは最初だけ」です。ポイントを振り返ります。
- DTOは、異なるレイヤー間でデータを転送するための専用オブジェクトである。
- EntityはDBのテーブルを表現し、DTOは画面やAPIの都合に合わせたデータ構造を持つ。
- DAO(Repository)はDBを読み書きする処理クラスで、DAOが取得したEntityをDTOに詰め替えて返すのが基本の流れ。
- DTOを使うことで、セキュリティの向上と、フロントエンドとバックエンドの独立性(変更に強い設計)という大きなメリットが得られる。
- 実装時には、LombokやMapStructなどのライブラリを活用すると、コードを簡潔に保てる。
- Record型を使えば不変なDTOをシンプルに書け、setterの誤用やequals/hashCodeの手動テストの手間も減らせる。
- 本記事で紹介したアンチパターン4選(Entityの流用、巨大DTOの使い回し、ロジックの混入、可変DTOの多用)を避けるだけで、事故のリスクは大きく下がる。
DTOは一見冗長に見えます。しかし役割を理解すれば、各層を疎結合に保てます。結果として変更に強く、メンテナンスしやすい堅牢なシステムになります。
| REV. | DATE | 内容 |
|---|---|---|
| 6 | 2026-10-21 | 同じ説明の繰り返しを減らし、読みやすく整えた |
| 5 | 2026-10-15 | 図解を描き直し(図面のデザインにそろえた) |
| 4 | 2026-10-14 | 一人称と言い回しの表記をそろえた |
| 3 | 2026-08-22 | 2026年8月時点の情報で全体を見直し |
| … ほか1回の改訂 | ||
| 1 | 2025-10-11 | 初版 |




