Javaの .properties ファイルは、java.util.Properties の load() に Reader か InputStream を渡して読み込みます。日本語が文字化けするのは、load(InputStream) がファイルを ISO-8859-1 として読むためで、UTF-8 を指定した Reader を load(Reader) に渡すと直ります。

この記事では、ファイルパス・クラスパス・ResourceBundle の3方式を、コピーしてそのまま動くコードと OpenJDK 21.0.10 の実行結果で紹介します。文字化けの再現と直し方、Java 9 以降の ResourceBundle の UTF-8 既定、native2ascii がなくなったあとの代替、store() で書き出すときのエスケープ、バックスラッシュや BOM のつまずきまで、原因別の対処表つきでまとめました。Java 25 の仕様は公式 API ドキュメントで確認しています。

Propertiesファイル読み込みの早見表

方式 書き方(要点) 文字コード 向いている用途
ファイルパスから読む props.load(Files.newBufferedReader(path, UTF_8)) Reader で指定した文字コード jar の外に置く設定ファイル
クラスパスから読む getResourceAsStream("/app.properties") を InputStreamReader(in, UTF_8) で包んで load Reader で指定した文字コード jar に同梱する既定値
ResourceBundle で読む ResourceBundle.getBundle("messages", locale) Java 9 以降は UTF-8 が既定 言語ごとに切り替えるメッセージ
メソッド 文字コード 日本語をそのまま書いたファイル
load(InputStream) ISO-8859-1 固定 文字化けする(Unicodeエスケープなら読める)
load(Reader) Reader に指定したもの UTF-8 を指定すれば正しく読める
store(OutputStream, コメント) ISO-8859-1 固定 日本語は Unicodeエスケープで書き出される
store(Writer, コメント) Writer に指定したもの 日本語のまま書き出される
ResourceBundle.getBundle(...) UTF-8(不正なバイト列があると ISO-8859-1 で読み直す) 正しく読める(Java 9 以降)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java Propertiesファイルの読み込み方|文字化けの原因と直し方」

.propertiesファイルの書式

.properties は「キー=値」を1行ずつ並べたテキストファイルです。 この記事で使う conf/app.properties(UTF-8 で保存)は次の内容です。

# アプリ設定
app.name=在庫管理
app.greeting=こんにちは
app.max=20
書式 ルール
コメント 行頭が # または !
区切り =、:、または空白。最初に現れたものがキーと値の境目
前後の空白 キーの前後と値の先頭の空白は除かれる。値の末尾の空白は残る
行の継続 行末に \ を置くと次の行へ続く(次の行の先頭の空白は除かれる)
エスケープ \t(タブ)、\n(改行)、\\(バックスラッシュ自体)、\ と u と16進4桁(Unicodeエスケープ)
値の型 すべて文字列。数値は Integer.parseInt などで変換する

方式1:ファイルパスから読み込む(文字化けの再現と直し方)

jar の外に置いた設定ファイルは、Files.newBufferedReader(path, StandardCharsets.UTF_8) で開いて load(Reader) に渡します。 比較のため、load(InputStream) で読んだ場合も並べます。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;

public class LoadFile {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("conf/app.properties");   // UTF-8 で保存したファイル

        // 直す前:load(InputStream) は ISO-8859-1 として読む
        Properties p1 = new Properties();
        try (InputStream in = Files.newInputStream(path)) {
            p1.load(in);
        }
        System.out.println("load(InputStream): " + p1.getProperty("app.name") + " / " + p1.getProperty("app.greeting"));

        // 直した後:load(Reader) に UTF-8 の Reader を渡す
        Properties p2 = new Properties();
        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            p2.load(reader);
        }
        System.out.println("load(Reader)     : " + p2.getProperty("app.name") + " / " + p2.getProperty("app.greeting"));

        // 値の取り出し:文字列で返るので、数値は変換する
        int max = Integer.parseInt(p2.getProperty("app.max"));
        String mode = p2.getProperty("app.mode", "standard");   // キーがないときの既定値
        System.out.println("max = " + max + ", mode = " + mode + ", 未定義キー = " + p2.getProperty("app.none"));
        System.out.println("キー一覧 = " + new java.util.TreeSet<>(p2.stringPropertyNames()));
    }
}

実行結果(java LoadFile.java、OpenJDK 21.0.10。1行目は画面に出ない制御文字を除いて掲載):

load(InputStream): å¨åº«ç®¡ç / ããã«ã¡ã¯
load(Reader)     : 在庫管理 / こんにちは
max = 20, mode = standard, 未定義キー = null
キー一覧 = [app.greeting, app.max, app.name]

同じファイルでも、load(InputStream) では文字化けし、load(Reader) に UTF-8 の Reader を渡すと正しく読めました。 直す前と後の違いは次の1か所だけです。

// 直す前:ISO-8859-1 として読まれる
try (InputStream in = Files.newInputStream(path)) {
    props.load(in);
}

// 直した後:UTF-8 として読まれる
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    props.load(reader);
}

値の取り出しでは、次の点を押さえておくと安心です。

  • getProperty(キー) は、キーがないと null を返します。getProperty(キー, 既定値) を使うと null を避けられます。
  • 値は文字列なので、数値は Integer.parseInt で変換します。
  • Properties は並び順を保持しません。一覧を表示するときは stringPropertyNames() をソートします。

なぜ文字化けするのか

load(InputStream) は、ファイルの1バイトを1文字(ISO-8859-1、別名 Latin-1)として読みます。 UTF-8 の日本語は1文字が3バイトなので、3バイトがばらばらの3文字として解釈されます。

文字 UTF-8 のバイト列 ISO-8859-1 として読んだ結果
在 E5 9C A8 å(E5)・制御文字(9C)・¨(A8)の3文字
庫 E5 BA AB å(E5)・º(BA)・«(AB)の3文字

Java 25 の API ドキュメントにも、load(InputStream) は「ISO 8859-1 文字エンコーディングを使うものとみなす」と明記されています。Java 18 以降は JVM の既定の文字コードが UTF-8 になりましたが(JEP 400)、load(InputStream) はその設定に関係なく ISO-8859-1 で読む仕様のままです。

方式2:クラスパスから読み込む

jar に同梱するファイルは、getResourceAsStream でクラスパスから開きます。 返ってくるのは InputStream なので、InputStreamReader に UTF-8 を指定して包んでから load に渡します。ここでは res/app.properties を置き、res をクラスパスに入れて実行します。

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Properties;

public class LoadClasspath {
    public static void main(String[] args) throws IOException {
        // クラスパスのルートにある app.properties を読む(jar の中でも同じ書き方で動く)
        Properties props = new Properties();
        try (InputStream in = LoadClasspath.class.getResourceAsStream("/app.properties")) {
            if (in == null) {
                throw new IllegalStateException("app.properties がクラスパスにありません");
            }
            try (Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
                props.load(reader);
            }
        }
        System.out.println(props.getProperty("app.name") + " / " + props.getProperty("app.greeting"));

        // パスの書き方の違い
        System.out.println("Class + \"/app.properties\"      : " + (LoadClasspath.class.getResource("/app.properties") != null));
        System.out.println("Class + \"app.properties\"       : " + (LoadClasspath.class.getResource("app.properties") != null));
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        System.out.println("ClassLoader + \"app.properties\" : " + (cl.getResource("app.properties") != null));
        System.out.println("ClassLoader + \"/app.properties\": " + (cl.getResource("/app.properties") != null));

        // null チェックを省いた場合
        try (InputStream in = LoadClasspath.class.getResourceAsStream("/none.properties")) {
            new Properties().load(in);
        } catch (NullPointerException e) {
            System.out.println(e);
        }
    }
}

実行結果(java -cp res LoadClasspath.java、OpenJDK 21.0.10):

在庫管理 / こんにちは
Class + "/app.properties"      : true
Class + "app.properties"       : true
ClassLoader + "app.properties" : true
ClassLoader + "/app.properties": false
java.lang.NullPointerException: inStream parameter is null
呼び出し方 パスの書き方 探す場所
Xxx.class.getResourceAsStream("/app.properties") 先頭に / クラスパスのルート
Xxx.class.getResourceAsStream("app.properties") / なし そのクラスと同じパッケージ
classLoader.getResourceAsStream("app.properties") / なし クラスパスのルート
classLoader.getResourceAsStream("/app.properties") 先頭に / 見つからない(null)

この例ではクラスがパッケージなし(ルート)にあるため2行目も true ですが、パッケージに属するクラスから / なしで呼ぶと、そのパッケージのフォルダを探します。 見つからないとき getResourceAsStream は例外を投げずに null を返すので、null チェックを入れておくと、原因がすぐわかるメッセージを出せます(最終行は null のまま load に渡した場合の例外です)。

Maven や Gradle の標準構成では、src/main/resources に置いたファイルがクラスパスのルートに入ります。

方式3:ResourceBundleで読み込む(Java 9以降はUTF-8既定)

ResourceBundle は、ロケール(言語・地域)に応じてファイルを切り替える仕組みです。 messages.properties と messages_ja.properties をクラスパスに置き、ベース名 messages を指定します(拡張子は書きません)。

res/messages.properties(既定):

title=Inventory
hello=Hello

res/messages_ja.properties(日本語用、UTF-8 で保存):

title=在庫管理
hello=こんにちは
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

public class LoadBundle {
    public static void main(String[] args) {
        // クラスパス上の messages_ja.properties / messages.properties を探す(拡張子は書かない)
        ResourceBundle ja = ResourceBundle.getBundle("messages", Locale.JAPANESE);
        System.out.println("ja: " + ja.getString("title") + " / " + ja.getString("hello"));

        ResourceBundle en = ResourceBundle.getBundle("messages", Locale.ROOT);
        System.out.println("root: " + en.getString("title") + " / " + en.getString("hello"));

        try {
            ja.getString("none");
        } catch (MissingResourceException e) {
            System.out.println(e);
        }
        if (args.length > 0) {
            ResourceBundle sjis = ResourceBundle.getBundle(args[0], Locale.JAPANESE);
            System.out.println(args[0] + ": " + sjis.getString("title"));
        }
    }
}

実行結果(java -cp res LoadBundle.java、OpenJDK 21.0.10):

ja: 在庫管理 / こんにちは
root: Inventory / Hello
java.util.MissingResourceException: Can't find resource for bundle java.util.PropertyResourceBundle, key none

Java 9 以降、ResourceBundle が読む .properties は UTF-8 が既定です(JEP 226)。日本語をそのまま書いたファイルが、追加の指定なしで読めます。挙動を切り替えるシステムプロパティを付けた場合も試しました。

条件 実行結果(OpenJDK 21.0.10)
指定なし、UTF-8 のファイル 在庫管理 / こんにちは
-Djava.util.PropertyResourceBundle.encoding=ISO-8859-1、UTF-8 のファイル å¨åº«ç®¡ç / ããã«ã¡ã¯(Java 8 までと同じ読み方になり文字化け)
指定なし、Shift_JIS(MS932)で保存したファイル ÝÉÇ(例外は出ず、ISO-8859-1 で読み直されて文字化け)

3行目のとおり、UTF-8 として正しくないバイト列が見つかると、自動的に ISO-8859-1 で読み直す動きになっています(Java 25 の PropertyResourceBundle の API ドキュメントにも同じ説明があります)。例外にならないぶん気づきにくいので、ファイルは UTF-8 で保存する、と決めておくと安心です。

getString は、キーがないと null ではなく MissingResourceException を投げます。Properties との違いとして覚えておきます。

3方式の選び方

判断のポイント ファイルパス クラスパス ResourceBundle
置き場所 jar の外(フォルダを指定) jar の中・クラスパス上 jar の中・クラスパス上
ビルドし直さずに値を変える できる jar 内は不可 jar 内は不可
文字コードの指定 Reader で自由に指定 Reader で自由に指定 UTF-8 既定
ロケールごとの切り替え 自分で実装 自分で実装 自動(_ja、_en など)
キーがないとき null null MissingResourceException
戻り値 Properties(書き換え可) Properties(書き換え可) 読み取り専用
向いている用途 環境ごとに変える設定 既定値の同梱 画面やメール文面のメッセージ

「環境ごとに変える値は jar の外、既定値は jar の中」と分けて、外のファイルで上書きする構成がよく使われます。new Properties(defaults) のように既定値の Properties をコンストラクタに渡すと、見つからないキーは既定値側を探してくれます。

書き込み:store()とエスケープ

store() は、OutputStream に渡すと日本語を Unicodeエスケープ(バックスラッシュ+u+16進4桁)に変換し、Writer に渡すと日本語のまま書き出します。

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;

public class StoreDemo {
    public static void main(String[] args) throws IOException {
        Properties props = new Properties();
        props.setProperty("app.name", "在庫管理");
        props.setProperty("app.dir", "C:\\temp\\new");
        props.setProperty("key with space", " 先頭に空白");
        props.setProperty("app.url", "http://example.com/a=b");

        // store(OutputStream):ISO-8859-1 で書く。日本語は Unicode エスケープになる
        Path p1 = Path.of("out_stream.properties");
        try (OutputStream out = Files.newOutputStream(p1)) {
            props.store(out, "by OutputStream");
        }
        // store(Writer):Writer の文字コードでそのまま書く
        Path p2 = Path.of("out_writer.properties");
        try (Writer w = Files.newBufferedWriter(p2, StandardCharsets.UTF_8)) {
            props.store(w, "by Writer");
        }
        for (Path p : new Path[] {p1, p2}) {
            System.out.println("--- " + p);
            Files.readAllLines(p, StandardCharsets.UTF_8).stream()
                    .filter(line -> !line.startsWith("#")).sorted().forEach(System.out::println);
        }
        System.out.println("--- コメント行");
        Files.readAllLines(p1).stream().filter(l -> l.startsWith("#")).forEach(System.out::println);

        // エスケープ形式のファイルは、どちらの load でも正しく読める
        Properties back = new Properties();
        try (var in = Files.newInputStream(p1)) { back.load(in); }
        System.out.println("読み戻し: " + back.getProperty("app.name") + " / " + back.getProperty("app.dir")
                + " / [" + back.getProperty("key with space") + "]");
    }
}

実行結果(java StoreDemo.java、OpenJDK 21.0.10。コメント行以外をソートして表示):

--- out_stream.properties
app.dir=C\:\\temp\\new
app.name=\u5728\u5EAB\u7BA1\u7406
app.url=http\://example.com/a\=b
key\ with\ space=\ \u5148\u982D\u306B\u7A7A\u767D
--- out_writer.properties
app.dir=C\:\\temp\\new
app.name=在庫管理
app.url=http\://example.com/a\=b
key\ with\ space=\ 先頭に空白
--- コメント行
#by OutputStream
#Fri Oct 02 09:39:55 JST 2026
読み戻し: 在庫管理 / C:\temp\new / [ 先頭に空白]
書き出される形 対象
\\ バックスラッシュ
\: と \= 区切り文字として解釈されうる : と =
\(バックスラッシュ+空白) キーの中の空白、値の先頭の空白
Unicodeエスケープ store(OutputStream) のときの日本語など(ASCII の範囲外)
# で始まる2行 1行目は引数のコメント、2行目は書き出した日時
  • エスケープは load が元に戻します(最終行)。手で書くファイルでは、: や = をエスケープしなくても、最初の区切り以降は値として読まれます。
  • store は元ファイルのコメントや並び順を保持しません。人が編集するファイルを書き戻すと体裁が変わるため、プログラムが書くファイルと人が書くファイルは分けておくと扱いやすくなります。
  • 日時のコメント行は、Java 25 の API ドキュメントによると、システムプロパティ java.properties.date を指定すれば任意の文字列に置き換えられます(差分を出したくないビルドで便利です。この指定は未実行で、公式ドキュメントでの確認です)。

native2asciiがなくなったあとの対応

native2ascii は、日本語を Unicodeエスケープに変換する JDK 付属のコマンドでしたが、Java 9 で JDK から除かれました。 手元の OpenJDK 21.0.10 の bin フォルダにも入っていないことを確認しています。現在の選択肢は次のとおりです。

方法 内容 向いている場面
UTF-8 のまま読む load(Reader) に UTF-8 の Reader を渡す。ResourceBundle は既定で UTF-8 自分のコードで読み込む場合(第一候補)
エスケープに変換する store(OutputStream) や下の変換コードで変換 読み込み側が ISO-8859-1 固定で、変更できない場合
IDE・ビルドツールの機能 エディタの自動変換表示や、ビルド時の変換タスク チームで運用が決まっている場合

UTF-8 のファイルをエスケープ形式へ変換する、単独で動く小さなコードです。

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

// native2ascii の代わり:UTF-8 のファイルを Unicode エスケープ形式へ変換する
public class ToEscape {
    public static void main(String[] args) throws Exception {
        String text = Files.readString(Path.of(args[0]), StandardCharsets.UTF_8);
        StringBuilder sb = new StringBuilder();
        for (char c : text.toCharArray()) {
            if (c < 0x80) sb.append(c);
            else sb.append(String.format("\\u%04x", (int) c));
        }
        Files.writeString(Path.of(args[1]), sb, StandardCharsets.ISO_8859_1);
        System.out.print(sb);
    }
}

実行結果(java ToEscape.java conf/app.properties conf/app_escaped.properties、OpenJDK 21.0.10):

# \u30a2\u30d7\u30ea\u8a2d\u5b9a
app.name=\u5728\u5eab\u7ba1\u7406
app.greeting=\u3053\u3093\u306b\u3061\u306f
app.max=20

エスケープ形式のファイルは ASCII 文字だけで書かれているため、load(InputStream) でも load(Reader) でも同じように読めます。

読み込み以外で起きる文字化けとつまずき

Shift_JISで保存されたファイル・BOM付きUTF-8・バックスラッシュ

次の3種類のファイルを用意して、読み込み結果を確かめます。conf/path.properties の内容は次のとおりです。

dir1=C:\temp\new
dir2=C:\\temp\\new
dir3=C:/temp/new
msg = hello world  
long=a,\
     b,\
     c
key\ with\ space=v
url:http://example.com/a=b
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;

public class Pitfalls {
    static Properties load(String file, Charset cs) throws IOException {
        Properties p = new Properties();
        try (BufferedReader r = Files.newBufferedReader(Path.of(file), cs)) { p.load(r); }
        return p;
    }

    public static void main(String[] args) throws IOException {
        // 1. Shift_JIS(MS932)で保存されたファイル
        try {
            load("conf/app_sjis.properties", StandardCharsets.UTF_8);
        } catch (IOException e) {
            System.out.println("SJISをUTF-8で   : " + e);
        }
        System.out.println("SJISをMS932で   : " + load("conf/app_sjis.properties", Charset.forName("MS932")).getProperty("app.name"));

        // 2. BOM 付き UTF-8:先頭のキーに BOM が混ざる
        Properties bom = load("conf/app_bom.properties", StandardCharsets.UTF_8);
        System.out.println("BOM付き app.name: " + bom.getProperty("app.name") + " / app.max: " + bom.getProperty("app.max"));
        System.out.println("BOM付き キー    : " + bom.stringPropertyNames().stream()
                .map(k -> k.length() + "文字").sorted().toList());

        // 3. バックスラッシュ・空白・継続行・区切り文字
        Properties p = load("conf/path.properties", StandardCharsets.UTF_8);
        System.out.println("dir1 = [" + p.getProperty("dir1").replace("\t", "<TAB>").replace("\n", "<改行>") + "]");
        System.out.println("dir2 = [" + p.getProperty("dir2") + "]");
        System.out.println("dir3 = [" + p.getProperty("dir3") + "]");
        System.out.println("msg  = [" + p.getProperty("msg") + "]");
        System.out.println("long = [" + p.getProperty("long") + "]");
        System.out.println("key with space = [" + p.getProperty("key with space") + "]");
        System.out.println("url  = [" + p.getProperty("url") + "]");
    }
}

実行結果(java Pitfalls.java、OpenJDK 21.0.10):

SJISをUTF-8で   : java.nio.charset.MalformedInputException: Input length = 1
SJISをMS932で   : 在庫管理
BOM付き app.name: null / app.max: 20
BOM付き キー    : [7文字, 9文字]
dir1 = [C:<TAB>emp<改行>ew]
dir2 = [C:\temp\new]
dir3 = [C:/temp/new]
msg  = [hello world  ]
long = [a,b,c]
key with space = [v]
url  = [http://example.com/a=b]
現象 理由 対処
MalformedInputException Shift_JIS のファイルを UTF-8 として読んだ ファイルを UTF-8 で保存し直す。変えられないときは Charset.forName("MS932") を指定
先頭のキーだけ null BOM(ファイル先頭の目印)がキー名の一部になった。app.name は8文字のはずが9文字 BOM なしの UTF-8 で保存する
C:\temp\new が崩れる \t がタブ、\n が改行として解釈された \\ と2つ重ねるか、/ で書く
値の末尾に空白が残る 末尾の空白は値の一部 不要な空白を消す。読み込み側で strip() する
複数行の値 行末の \ で継続。次の行の先頭の空白は除かれる 長い値を見やすく折り返すのに使える

読み込みは正しいのに画面で化ける

ファイルを正しく読めていても、出力先の文字コードが合っていないと表示が崩れます。 文字コードに関するシステムプロパティを表示して確かめます。

public class Enc {
    public static void main(String[] args) {
        for (String k : new String[] {"file.encoding", "native.encoding", "stdout.encoding", "sun.jnu.encoding"}) {
            System.out.println(k + " = " + System.getProperty(k));
        }
        System.out.println("こんにちは");
    }
}

実行結果(OpenJDK 21.0.10、Linux。ロケールを変えて3通り):

$ LANG=C.UTF-8 java Enc.java
file.encoding = UTF-8
native.encoding = UTF-8
stdout.encoding = UTF-8
sun.jnu.encoding = UTF-8
こんにちは

$ LANG=C java Enc.java
file.encoding = UTF-8
native.encoding = ANSI_X3.4-1968
stdout.encoding = ANSI_X3.4-1968
sun.jnu.encoding = ANSI_X3.4-1968
?????

$ LANG=C java -Dstdout.encoding=UTF-8 Enc.java
file.encoding = UTF-8
native.encoding = ANSI_X3.4-1968
stdout.encoding = UTF-8
sun.jnu.encoding = ANSI_X3.4-1968
こんにちは

file.encoding が UTF-8 でも、stdout.encoding(標準出力の文字コード)が別の値だと、System.out.println の日本語が ? になります。 コンテナやバッチ実行環境でロケールが設定されていないときに起こりやすく、-Dstdout.encoding=UTF-8 を付けるか、環境変数 LANG を UTF-8 のロケールにすると解消します。「値が ? になる」ときは出力側、「å のような記号の並びになる」ときは読み込み側、と見分けると切り分けが早くなります。

文字化けの原因別対処表

症状 原因 対処
å や ã が並ぶ UTF-8 のファイルを load(InputStream) で読んだ load(Reader) に UTF-8 の Reader を渡す
同上(ResourceBundle、Java 8) Java 8 までは ISO-8859-1 で読む Java 9 以降へ上げる。または Unicodeエスケープで書く
同上(ResourceBundle、Java 9 以降) Shift_JIS など UTF-8 以外で保存され、ISO-8859-1 で読み直された ファイルを UTF-8 で保存し直す
MalformedInputException UTF-8 以外のファイルを UTF-8 の Reader で読んだ 保存時の文字コードをそろえる
先頭のキーだけ取れない BOM 付き UTF-8 BOM なしで保存する
? になる 標準出力やログの文字コードが UTF-8 ではない -Dstdout.encoding=UTF-8、ロケールの設定を確認
エディタでは読めるが実行すると化ける エディタの表示用の文字コードと、Java の読み込み方が別 保存時の文字コードと load の指定をそろえる
バックスラッシュ+u の文字列がそのまま表示される ファイル内で \\u と二重にエスケープされている バックスラッシュを1つにする
Windows のパスが崩れる \t や \n がエスケープとして解釈された \\ と重ねるか / で書く

確認の手順は、次の順番がおすすめです。

  1. ファイルの保存時の文字コード(UTF-8・BOM なし)を確認する
  2. 読み込みが load(Reader) で、UTF-8 を指定しているか確認する
  3. 読み込んだ値の length() を表示する(「在庫管理」が4なら読み込みは正常、12なら読み込み側の問題)
  4. 4文字なのに表示が崩れるなら、出力側(stdout.encoding、ログ、画面の文字コード)を確認する

Spring Bootのapplication.propertiesとの違い

Spring Boot の application.properties は、書式は同じ .properties ですが、読み込みと値の受け取り方を Spring Boot が担当します。 自分で Properties.load を呼ぶ必要はありません。

比べる点 java.util.Properties Spring Boot の application.properties
読み込み 自分で load を呼ぶ 起動時に自動で読み込まれる
値の受け取り getProperty("キー")(文字列) @Value や @ConfigurationProperties で型つきの値として受け取る
既定の文字コード load(InputStream) は ISO-8859-1、load(Reader) は指定どおり ISO-8859-1(日本語は Unicodeエスケープで書くか、application.yml を使う)
環境ごとの切り替え 自分で実装 プロファイル(application-dev.properties など)
環境変数での上書き 自分で実装 標準で対応

Spring Boot 側の文字コードの扱い(ISO-8859-1 で読まれることの確認や、spring.config.import で文字コードを指定する方法)と設定項目の一覧は、当サイトの記事「Spring Boot application.properties設定一覧」で詳しく扱っています。この表のうち Spring Boot の列は、その記事と公式ドキュメントに基づく内容で、今回あらためて実行したものではありません。

よくある質問

Q. load(InputStream)とload(Reader)はどちらを使えばよいですか? A. 日本語などをそのまま書くファイルなら、load(Reader) に UTF-8 の Reader を渡す形をおすすめします。load(InputStream) は ISO-8859-1 固定のため、ASCII だけ、または Unicodeエスケープで書かれたファイル向けです。

Q. native2asciiは今でも必要ですか? A. 自分のコードで読み込むなら不要です。load(Reader) と、Java 9 以降の ResourceBundle は UTF-8 のファイルをそのまま読めます。読み込み側が ISO-8859-1 固定で変えられない場合だけ、エスケープ形式へ変換します(この記事の ToEscape.java や store(OutputStream) が使えます)。

Q. Propertiesの読み込み順を、ファイルに書いた順にできますか? A. Properties は順序を保持しない仕様です。表示や出力で順序が必要なときは、stringPropertyNames() をソートするか、ファイルを1行ずつ読んで LinkedHashMap に入れる方法があります。

Q. Java 25でも同じコードで動きますか? A. Java 25 の API ドキュメントで、load(InputStream) が ISO 8859-1、load(Reader) と store(Writer) が指定した文字コード、ResourceBundle が UTF-8 既定という仕様が Java 21 と同じであることを確認しました。この記事のコードはそのまま使える見込みです(Java 25 での実行は行っていません)。

まとめ

  • .properties の読み込みは ファイルパス・クラスパス・ResourceBundle の3方式。用途で選ぶ
  • load(InputStream) は ISO-8859-1 固定。UTF-8 の日本語は文字化けする
  • 直し方は load(Reader) に UTF-8 の Reader を渡すこと。変更は1か所で済む
  • ResourceBundle は Java 9 以降 UTF-8 が既定。UTF-8 以外で保存すると ISO-8859-1 で読み直される
  • store(OutputStream) は Unicodeエスケープ、store(Writer) は日本語のまま書き出す
  • native2ascii は JDK に含まれなくなった。UTF-8 のまま読むのが第一候補
  • ? になるときは出力側(stdout.encoding)、BOM・Shift_JIS・バックスラッシュも確認する

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java Propertiesファイルの読み込み方|文字化けの原因と直し方」

株式会社RJCでは、Propertiesファイルと文字コードの扱いのような基礎を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?