概要
API 連携やファイル保存で、record で定義した DTO をシリアライズする場面は少なくありません。record は Serializable を implements すればバイナリ直列化に対応しますが、従来のクラスとは復元の仕組みが異なる点があります。また、現場では JSON 形式でのやり取りが主流であり、Jackson や Gson なしで最低限の JSON 変換を Pure Java で行いたいケースもあります。この記事では、record の標準シリアライズ(ObjectOutputStream / ObjectInputStream)の挙動と注意点、手動 JSON 変換の実装パターン、そして Java 21 で sealed interface と組み合わせた型安全な JSON 生成までを扱います。外部ライブラリを使わずに動く完結したコードを示しつつ、実務で Jackson に移行すべきラインについても触れます。
使いどころ
マイクロサービス間の HTTP レスポンスを record で受け取り、手動 JSON 変換でログ出力する
バッチ処理の中間結果を record のバイナリシリアライズで一時ファイルに保存し、障害時にリトライする
設定ファイルの読み込み結果を record に詰めて返却し、変更不可の設定値オブジェクトとして扱う
コード例
import java.io.*;
public class RecordSerializeDemo {
// Serializable を明示的に implements
record UserDto(String id, String name, int age) implements Serializable {
@SuppressWarnings("unused")
private static final long serialVersionUID = 1L;
}
// Pure Java での手動 JSON 変換
static String toJson(UserDto user) {
return "{\"id\":\"" + user.id() + "\","
+ "\"name\":\"" + user.name() + "\","
+ "\"age\":" + user.age() + "}";
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
var user = new UserDto("U001", "田中太郎", 30);
System.out.println("元オブジェクト: " + user);
var json = toJson(user);
System.out.println("JSON: " + json);
byte[] bytes;
try (var baos = new ByteArrayOutputStream();
var oos = new ObjectOutputStream(baos)) {
oos.writeObject(user);
bytes = baos.toByteArray();
}
System.out.println("シリアライズ済みバイト数: " + bytes.length);
try (var bais = new ByteArrayInputStream(bytes);
var ois = new ObjectInputStream(bais)) {
var restored = (UserDto) ois.readObject();
System.out.println("復元: " + restored);
System.out.println("元と一致: " + user.equals(restored));
}
}
}Version Coverage
record に Serializable を implements すればバイナリ直列化が可能。アクセサが name() 形式なので JSON 変換のコードも簡潔に書ける。
// Java 17: record で簡潔に定義 + JSON 変換
record UserDto(String id, String name, int age)
implements Serializable {
private static final long serialVersionUID = 1L;
}
static String toJson(UserDto u) {
return "{\"id\":\"" + u.id() + "\","
+ "\"name\":\"" + u.name() + "\","
+ "\"age\":" + u.age() + "}";
}Library Comparison
注意点
record は Serializable を自動で implements しない。バイナリ直列化が必要な場合は明示的に implements Serializable を付ける
record のデシリアライズはコンストラクタ経由で行われるため、従来クラスの readObject / writeObject とは復元メカニズムが異なる。カスタムシリアライズは基本的に不要
serialVersionUID は record でも定義を推奨する。フィールドの追加・削除時に互換性エラーを明示的に検知できる
手動 JSON 変換で文字列フィールドにダブルクォートやバックスラッシュが含まれる場合、エスケープ処理が必要になる。本番コードでは Jackson の導入を検討すべきライン
record を JSON に変換する際、null フィールドの扱い(省略するか null として出すか)を事前に決めておかないと、受け取り側でパースエラーになることがある
FAQ
一時ファイルへの中間保存やキャッシュなど、Java 同士でしかやり取りしない場面では有効です。ただし外部システムとの連携では JSON や Protocol Buffers のほうが互換性・可読性の面で有利です。
API 仕様で null の扱いを決めておくのが前提です。手動変換では null チェックを入れて省略または "null" を出力します。Jackson なら @JsonInclude で制御できます。
必須ではありませんが、定義しておくとフィールド構成の変更時に InvalidClassException で早期に検知できます。永続化やキャッシュに使う record では付けておくことを推奨します。