本文へスキップ
AZ Tools

Java .properties パーサー

.properties は最も単純に見える設定形式ですが、自作パーサーが最も間違えやすい形式でもあります。このページは java.util.Properties.load が実際に使う手順(JDK の LineReader、続いて load0 と loadConvert)をそのまま実行し、貼り付けたテキストがどんなマップになるかをキーごとに、また JSON と Properties.store が書き戻す形でも表示します。 区切り文字は =、: と空白です。したがって "c three" はキー c、値 three であり、"key with spaces=x" はキーが "key"、値が "with spaces=x" になります。区切り文字の前後の空白は消えますが、値の末尾の空白は残ります。行末のバックスラッシュは次の行を連結し、その行の字下げは取り除かれますが、バックスラッシュが偶数個なら継続しません。コメント行もバックスラッシュで終わっても継続しません。# と ! は行の最初の非空白文字のときだけコメントを開くので、URL の断片にある # は値に残ります。エスケープは \t \n \r \f と \uXXXX だけで、他のバックスラッシュは黙って消えます。 実務で最大の問題は文字コードです。ほぼすべてのフレームワークが呼ぶ load(InputStream) はバイト列を ISO-8859-1 として読むため、UTF-8 で保存したファイルは文字化けします。入力に非 ASCII が含まれるとき、このページは両方の読み方を並べて示し、変わってしまうキーを名指しします。 すべてブラウザ内で動き、アップロードは行いません。ただし重複したキーのどちらが意図だったか、おかしく見える値が本当に間違いかまでは分かりません。この道具が示すのは JDK がこのバイト列に何をするかであって、あなたが何を書きたかったかではありません。

.properties ファイルの内容

一意なキー

11

代入の数

12

コメント行

1

継続した行

1

指摘

6

バイト列の読み方:

ほぼすべてのフレームワークの既定である load(InputStream) は常に ISO-8859-1 で読みます。二つの読み方を切り替えて、どのキーが変わるか確かめてください。

パーサーが気づいたこと
  • 非ASCII文字が文字化け · 行 10

    load(InputStream) はバイト列を ISO-8859-1 として読みます。UTF-8 で保存したファイルでは 1 文字が 2〜3 文字に化けます。\uXXXX で書くか、文字セットを指定した Reader を使ってください。

    • greeting: "Grüße" → "Grüße"
  • 空白が区切り文字 · 行 4

    この行には = も : もありません。最初の空白やタブがキーと値を分けるので "c three" は c = three です。

    app.mode

  • 末尾の空白が残る · 行 9

    区切り文字の前後の空白は捨てられますが、値の末尾の空白は値の一部です。エディタにも見えず、エラーにもなりません。

    app.token (+3)

  • キーの中の区切り文字 · 行 6

    このキーにはエスケープされた =、: または空白が含まれます。1 つのキーであり、最初の "=" で分割するコードは誤った位置で切ります。

    menu.label 1

  • コメントではない · 行 14

    # と ! が コメントを開くのは行頭の最初の非空白文字のときだけです。ここでは値の後ろにあるので値に含まれます。

    note

  • 重複したキー · 行 2, 12

    同じキーが複数回指定されています。load は最後の値だけを残し、何も警告しません。前の行は生きて見えるだけの死んだ設定です。

    app.name → "orders-api-v2"

キー区切り備考
1# Spring Boot style configuration — and every trap in the formatコメント
2app.nameorders-api=上書き済み
3server.port8080:
4app.modeproduction空白空白が区切り文字
5jdbc.urljdbc:postgresql://db:5432/orders?ssl=true=
6menu.label 1Save as…=キーの中の区切り文字
7–8welcome.messageHello and welcome=
9app.tokenabc123···=末尾の空白が残る
10greetingGrüße=
11greeting.escapedGrüße=
12app.nameorders-api-v2=
13pathC:\\Users\\dev\\app=
14notevalue # this is not a comment=コメントではない

表では制御文字を \n や \t に置き換え、値末尾の空白を · で見えるようにしています。JSON ビューはそのままの値です。

使い方

  1. ファイルの内容を貼り付けるか、.properties ファイルをドロップ領域に落としてください。ブラウザ内で読むだけでアップロードはしません。
  2. 行ごとの表を見ます。各項目が何行目から来たか、実際に使われた区切り文字は何か、その行に変わった点があるかがバッジで示されます。
  3. 上の指摘を順に片付けます。重複キー、値末尾の空白、次の項目を飲み込んだ継続行の三つが、本番で静かに挙動を変えます。
  4. 非 ASCII があるときは UTF-8 と ISO-8859-1 の読み方を切り替え、load(InputStream) がアプリに何を渡すかを確認します。
  5. 設定の差分には JSON ビューを、書き戻すファイルには Properties.store ビューをコピーしてください。区切り文字も非 ASCII もすべてエスケープされます。

よくある質問

"c three" に = がないのに、なぜキーと値に分かれるのですか。
この形式では空白も区切り文字だからです。Properties.load はエスケープされていない最初の =、: または空白文字を探し、その手前をキーとします。そのため "c three" はキー c と値 three になり、"key with spaces=x" はキーが "key"、値が "with spaces=x" となって = は区切り文字になれません。キーに空白を入れたい場合は "\ " のようにエスケープする必要があり、Properties.store もそうしたキーを書き戻すときはまさにその形にします。
日本語やアクセント付き文字が化けます。何が起きていますか。
java.util.Properties.load(InputStream) は、ストリームを ISO-8859-1 として 1 バイト 1 文字で読むと規定されています。UTF-8 で保存したファイルでは 1 文字が 2〜3 バイトであり、その各バイトがラテン文字になって化けます。例外も出ないので、壊れた文字列は表示まで届きます。対処は三つです。非 ASCII を \uXXXX で書く(native2ascii と Properties.store のやり方)、UTF-8 の InputStreamReader で load(Reader) を呼ぶ、または既定が UTF-8 の loadFromXML を使うことです。
値の末尾の空白は本当に残るのですか。
残ります。そしてそれが一群のバグ報告を生みます。行頭の空白、区切り文字の前後の空白、継続行の字下げはすべて捨てられますが、値の末尾の空白はそのままです。そこで行が終わり、誰も削らないからです。空白が一つ紛れたパスワードや URL は構文エラーなく解析され、接続だけが失敗し、エディタでもレビューでも正常なものと見分けがつきません。このページの表はその文字を目に見える形で示します。
どんな行が次の行へ継続しますか。
バックスラッシュが奇数個で終わるときです。最後のバックスラッシュが取り除かれ、次の行が連結され、その行の先頭の空白は削られます。偶数個なら継続せず、バックスラッシュは半分の個数の実文字として残ります。ここから二つの落とし穴が生まれます。値の末尾にバックスラッシュが紛れると、その下の "key=value" がその値に飲み込まれ、そのキーは消えます。またコメント行はバックスラッシュで終わっても継続しないので、次の行は普通の項目として解析されます。
同じキーを二行で指定したら、どちらが勝ちますか。
後の行が、何も言わずに勝ちます。Properties は Hashtable であり、load は項目ごとに put を呼ぶだけなので、後の行が前の行を上書きします。警告も例外も記録もありません。断片を集めてファイルを作ったときや、同じキーを一度は = で、一度は : で書いたときに、うっかり作りやすい状態です。このページは重複したキーと、それを指定したすべての行番号を並べ、負けた行を薄く表示します。
.env ファイルと同じ形式ですか。
違います。同じだと思い込むことが、静かな障害のよくある原因です。.env には空白区切りがなく、引用符で値を保護したり複数行を収めたりし、値の後ろの # をインラインコメントとして扱い、${VAR} 展開に対応することも多いです。.properties にはそれらがありません。引用符は値に入る普通の文字で、値の後ろの # も値の一部、形式そのものに変数展開はなく(Spring が上に載せているだけです)、継続は引用符ではなくバックスラッシュで行います。

関連ツール