BOM(Byte Order Mark)とは何か、なぜ業務で「除去」が必要になるのか
BOM(Byte Order Mark)は、テキストファイルの先頭に付く 「このファイルは UTF-8 だよ」 という目印のようなバイト列です。 UTF-8 の BOM は次の 3 バイトです。
EF BB BF
見た目には出ませんが、ファイルの先頭にこっそり存在します。
業務では、この BOM が原因で次のようなトラブルが頻発します。
- CSV の 1 行目の先頭に「謎の文字」が混ざる
- システムが「ヘッダ名が一致しない」と怒る
- JSON の先頭に BOM があるとパーサがエラーを出す
- 外部システムが BOM 付き UTF-8 を受け付けない
つまり、BOM は「人間には見えないのに、プログラムには影響する」厄介者です。 だからこそ、業務では BOM を確実に除去するユーティリティ が必要になります。
BOM の正体を理解する(初心者向けに噛み砕く)
BOM は「文字コードのヒント」だが、UTF-8 では本来不要
UTF-16 や UTF-32 では、エンディアン(バイト順)を示すために BOM が必要です。 しかし UTF-8 はエンディアンの概念がないため、本来 BOM は不要です。
それでも、Windows 系のツールや一部のエディタは「UTF-8 with BOM」を生成します。 これが業務システムに流れ込むと、文字化けやパースエラーの原因になります。
Java で BOM を除去する基本ユーティリティ
ファイルの先頭 3 バイトをチェックして、BOM なら取り除く
最もシンプルな BOM 除去ユーティリティは次のようになります。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class BomUtils {
private static final byte[] UTF8_BOM = {(byte)0xEF, (byte)0xBB, (byte)0xBF};
public static byte[] removeUtf8Bom(byte[] bytes) {
if (bytes.length >= 3 &&
bytes[0] == UTF8_BOM[0] &&
bytes[1] == UTF8_BOM[1] &&
bytes[2] == UTF8_BOM[2]) {
byte[] result = new byte[bytes.length - 3];
System.arraycopy(bytes, 3, result, 0, result.length);
return result;
}
return bytes;
}
public static byte[] readFileWithoutBom(Path path) throws IOException {
byte[] bytes = Files.readAllBytes(path);
return removeUtf8Bom(bytes);
}
}
Java使い方の例です。
Path path = Path.of("data/input.csv");
byte[] bytes = BomUtils.readFileWithoutBom(path);
String text = new String(bytes, java.nio.charset.StandardCharsets.UTF_8);
System.out.println(text);
Javaここで深掘りしたいポイントは 「BOM はバイト列の問題であり、文字列の問題ではない」 ということです。 つまり、BOM を除去するのは 文字列に変換する前 に行う必要があります。
ストリームで BOM を除去する(大きなファイル向け)
先頭 3 バイトだけチェックし、残りはそのまま流す
巨大ファイルでは readAllBytes() が使えないため、ストリームで BOM を除去します。
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class BomStreamUtils {
private static final byte[] UTF8_BOM = {(byte)0xEF, (byte)0xBB, (byte)0xBF};
public static void copyWithoutBom(InputStream in, OutputStream out) throws IOException {
byte[] firstBytes = new byte[3];
int read = in.read(firstBytes);
if (read == 3 &&
firstBytes[0] == UTF8_BOM[0] &&
firstBytes[1] == UTF8_BOM[1] &&
firstBytes[2] == UTF8_BOM[2]) {
// BOM をスキップ
} else {
if (read > 0) {
out.write(firstBytes, 0, read);
}
}
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
}
Javaここで深掘りしたいのは 「BOM は必ず先頭にしか存在しない」 という性質です。 だからこそ、先頭 3 バイトだけチェックすれば十分です。
BOM 除去ユーティリティが業務で役立つ具体例
CSV インポートでのトラブル防止
外部ベンダーから送られてくる CSV が「UTF-8 with BOM」だと、 1 行目の先頭に BOM が混ざり、ヘッダ名が一致しなくなります。
例: 本来 → id,name,email BOM 付き → id,name,email
これを防ぐために、インポート処理の最初で BOM を除去します。
JSON パースエラーの防止
JSON の先頭に BOM があると、 多くの JSON パーサが「不正な文字」としてエラーを出します。
API 連携で JSON を受け取る場合、 BOM 除去ユーティリティを通してからパースすると安全です。
ログ解析の安定化
ログファイルが BOM 付きだと、 ログ解析ツールが最初の行を正しく読めないことがあります。
業務バッチでログを取り込む前に BOM を除去しておくと、 解析処理が安定します。
セキュリティ・運用の観点から見た BOM 除去
「見えないデータ」を扱うという意識が重要
BOM は画面に表示されないため、 人間が気づかないままシステムが壊れることがあります。
だからこそ、次のような運用が有効です。
- インポート処理の最初に必ず BOM 除去を入れる
- 外部システムとの連携仕様に「UTF-8 without BOM」を明記する
- テストデータに「BOM 付きファイル」を含めて検証する
BOM はセキュリティ問題ではありませんが、 「静かに壊れる」という点で運用事故の原因になりやすい存在です。
まとめ:BOM 除去ユーティリティで身につけてほしい感覚
BOM 除去ユーティリティは、「見えない先頭バイトを確実に取り除く」ための小さな道具ですが、 その裏には次のような大事なポイントがあります。
- BOM はバイト列の問題であり、文字列にする前に除去する
- UTF-8 BOM は必ず先頭 3 バイト(EF BB BF)
- 小さなファイルは
readAllBytes、大きなファイルはストリームで除去する - CSV・JSON・ログなど、業務データの安定性に直結する
- 「見えないデータ」を扱うという意識を持つことで、運用事故を防げる
もしあなたのプロジェクトで、 「CSV のヘッダがなぜか一致しない」「JSON パースが時々失敗する」 といった謎のトラブルがあるなら、 BOM が原因かもしれません。
