概要
銀行の全銀フォーマット、EDI の固定長電文、メインフレーム連携のデータ交換など、固定長ファイルは旧来のシステム連携でいまだに現役です。フィールドの開始位置と長さが仕様書で決められており、カンマやタブのような区切り文字は使いません。Java では substring() でフィールドを切り出し、String.format() でゼロ埋めやスペース埋めのフォーマットを行うのが基本ですが、バイト数と文字数の違いや、トリム処理の漏れなど、実装時に踏みやすい落とし穴があります。この記事では、固定長レコードの読み書きを record 型で型安全に実装し、実務でそのまま使えるパターンを整理します。
使いどころ
銀行の全銀フォーマット(振込データ)を Java で生成し、ファイル転送する
メインフレームから送られてくる固定長レコードを読み込み、DB に取り込む
EDI の電文を固定長フォーマットに変換して取引先に送信する
コード例
import java.io.BufferedReader;
/**
* 固定長ファイルの読込・書込。
* レコード形式(1行48文字):
* [0-3] 社員番号 4桁 右詰め・スペース埋め
* [4-23] 氏名 20桁 左詰め・スペース埋め
* [24-31] 部署 8桁 左詰め・スペース埋め
* [32-39] 給与 8桁 右詰め・ゼロ埋め
* [40-47] 入社日 8桁 yyyyMMdd
*/
public class FixedLengthRecords {
record EmployeeRecord(int id, String name, String department,
int salary, String joinDate) {}
/** レコードを固定長1行にフォーマット */
public static String format(EmployeeRecord rec) {
return "%4d".formatted(rec.id())
+ "%-20s".formatted(rec.name())
+ "%-8s".formatted(rec.department())
+ "%08d".formatted(rec.salary())
+ rec.joinDate();
}
/** 固定長1行をレコードにパース */
public static EmployeeRecord parse(String line) {
var id = Integer.parseInt(line.substring(0, 4).trim());
var name = line.substring(4, 24).trim();
var department = line.substring(24, 32).trim();
var salary = Integer.parseInt(line.substring(32, 40).trim());
var joinDate = line.substring(40, 48).trim();
return new EmployeeRecord(id, name, department, salary, joinDate);
}
public static void main(String[] args) throws IOException {
var records = List.of(
new EmployeeRecord(1, "Yamada Taro", "DEV", 450000, "20200401"),
new EmployeeRecord(2, "Suzuki Hanako", "SALES", 380000, "20210601"),
new EmployeeRecord(1234, "Tanaka Jiro", "HR", 320000, "20220401")
);
var sb = new StringBuilder();
for (var rec : records) {
sb.append(format(rec)).append(System.lineSeparator());
}
var fileContent = sb.toString();
System.out.println("=== 固定長フォーマット ===");
System.out.print(fileContent);
System.out.println("\n=== パース結果 ===");
try (var br = new BufferedReader(new StringReader(fileContent))) {
String line;
while ((line = br.readLine()) != null) {
if (!line.isBlank()) {
System.out.println(parse(line));
}
}
}
}
}Version Coverage
record(Java 16+)で不変なレコードクラスを簡潔に定義できる。var で型推論も使え、コード量が大幅に減る。
// Java 17: record で簡潔に定義
record EmployeeRecord(int id, String name,
String department, int salary, String joinDate) {}
String formatted = "%4d".formatted(rec.id())
+ "%-20s".formatted(rec.name());Library Comparison
注意点
日本語などのマルチバイト文字を含む場合は、文字数ではなくバイト数でフィールド位置を管理する必要がある。getBytes(charset).length でバイト長を確認すること。
substring() で切り出した値は必ず trim() すること。スペース埋めのフィールドをそのまま parseInt() すると NumberFormatException が発生する。
固定長ファイルでは改行コードの扱いが仕様によって異なる(CRLF / LF / 改行なし)。仕様書に従って改行コードを設定すること。
右詰めゼロ埋めのフォーマット(%08d)で負の値を渡すと、マイナス記号分だけ桁あふれする。入力値の妥当性を事前にチェックすること。
FAQ
仕様書に従いますが、全銀フォーマットなど多くの業務仕様はバイト数ベースです。Shift_JIS では全角1文字=2バイトで計算します。
書き込み前にバリデーションを行い、桁あふれする場合はエラーとして処理するのが安全です。無言で切り捨てると障害の原因になります。
Java 16 以降なら record を推奨します。不変性が保証され、equals/hashCode/toString が自動生成されるため、データ保持に適しています。