- JAVA
Java 環境変数の取得方法|System.getenvとシステムプロパティ一覧
Javaで環境変数を取得するには System.getenv("名前") を使います。値が設定されていないときは null が返ります。 よく似たものに System.getProperty("名前") がありますが、こちらが読むのは「システムプロパティ」で、java コマンドの -D オプションや JVM 自身が設定する、環境変数とは別の入れ物です。
この記事では、2つの違いを比較表にまとめ、Linux で export と -D を指定して実行した出力を載せます。あわせて、user.dir・user.home・java.version・file.encoding など主要なシステムプロパティの実値つき一覧、getenv の Map に書き込んだときの UnsupportedOperationException、Optional での既定値の付け方、ProcessBuilder で子プロセスに環境変数を渡す方法、JDK 18 以降の file.encoding と native.encoding の違いまで扱います。
確認環境:Ubuntu 24.04 / OpenJDK 21.0.10。出力例のユーザー名とパスは、表示を環境に合わせて置き換えています(ユーザー名 taro、ホーム /home/taro、作業ディレクトリ /home/taro/demo)。
環境変数とシステムプロパティの早見表
| やりたいこと | 書き方 | 無いときの値 |
|---|---|---|
| 環境変数を1つ取得 | System.getenv("APP_MODE") |
null |
| 環境変数を全部取得 | System.getenv()(読み取り専用の Map) |
― |
| 環境変数に既定値を付ける | System.getenv().getOrDefault("APP_MODE", "dev") |
既定値 |
| システムプロパティを1つ取得 | System.getProperty("app.mode") |
null |
| システムプロパティに既定値を付ける | System.getProperty("app.mode", "dev") |
既定値 |
| システムプロパティを全部取得 | System.getProperties() |
― |
| 数値のプロパティを取得 | Integer.getInteger("app.port", 8080) |
既定値 |
| 真偽値のプロパティを取得 | Boolean.getBoolean("app.debug") |
false |
| 実行時にプロパティを設定 | System.setProperty("app.mode", "test") |
― |
| 環境変数を設定して起動 | export APP_MODE=dev のあと java Main |
― |
| プロパティを設定して起動 | java -Dapp.mode=prod Main |
― |
2つの違いは次のとおりです。
| 項目 | 環境変数 | システムプロパティ |
|---|---|---|
| 取得メソッド | System.getenv(name) |
System.getProperty(key) |
| 設定する場所 | OS・シェル(export NAME=値) |
java コマンド(-Dkey=値)と JVM 自身 |
| 名前の書き方の慣習 | 大文字とアンダースコア(APP_MODE) | 小文字とドット(app.mode) |
| 見える範囲 | そのシェルから起動した全プロセスと、その子孫 | その JVM の中だけ |
| 実行中の変更 | できない(読み取り専用) | できる(System.setProperty) |
| 無いとき | null | null(第2引数で既定値を指定できる) |
| 既定値の指定 | Optional や getOrDefault で書く | getProperty(key, 既定値) |
| JVM が用意する値 | なし(OS が用意する HOME・PATH など) | あり(java.version、user.dir、os.name など) |
| 大文字と小文字 | UNIX 系では区別する。Windows では通常区別しない | 区別する |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java 環境変数の取得方法|System.getenvとシステムプロパティ一覧」
System.getenv と System.getProperty の基本
まず、両方を同じプログラムで読んでみます。
public class EnvBasic {
public static void main(String[] args) {
// 環境変数:OS(シェル)から渡される
System.out.println("APP_MODE (env) = " + System.getenv("APP_MODE"));
System.out.println("HOME (env) = " + System.getenv("HOME"));
System.out.println("NO_SUCH (env) = " + System.getenv("NO_SUCH"));
// システムプロパティ:java コマンドの -D や JVM が設定する
System.out.println("app.mode (prop) = " + System.getProperty("app.mode"));
System.out.println("user.home (prop) = " + System.getProperty("user.home"));
System.out.println("no.such (prop) = " + System.getProperty("no.such"));
System.out.println("no.such + 既定値 = " + System.getProperty("no.such", "default"));
}
}
何も指定せずに実行する
java EnvBasic.java
APP_MODE (env) = null
HOME (env) = /home/taro
NO_SUCH (env) = null
app.mode (prop) = null
user.home (prop) = /home/taro
no.such (prop) = null
no.such + 既定値 = default
設定していない APP_MODE と app.mode は、どちらも null です。空文字ではなく null なので、そのままメソッドを呼ぶと例外になります(後述)。HOME は OS が用意する環境変数、user.home は JVM が用意するシステムプロパティで、同じ場所を指しています。
export と -D を指定して実行する
export APP_MODE=dev
java -Dapp.mode=prod EnvBasic.java
APP_MODE (env) = dev
HOME (env) = /home/taro
NO_SUCH (env) = null
app.mode (prop) = prod
user.home (prop) = /home/taro
no.such (prop) = null
no.such + 既定値 = default
export した値は getenv で、-D で渡した値は getProperty で取れます。 名前が似ていても別の入れ物なので、APP_MODE を export しても getProperty("app.mode") には入りません。
その1回の実行だけに環境変数を渡したいときは、コマンドの前に書きます。
APP_MODE=stg java EnvBasic.java
APP_MODE (env) = stg
-D オプションの書き方と置く位置
-D の書き方には、つまずきやすい点が2つあります。
クラス名(ファイル名)より前に書く
java EnvBasic.java -Dapp.mode=prod
app.mode (prop) = null
クラス名やファイル名より後ろに書いた -D は、プログラムの引数(main の args)として渡されます。 システムプロパティにはならないので null のままです。-D は必ず java の直後、クラス名より前に置きます。
空白を含む値は引用符で囲む
java -Dapp.title="Hello World" -Dapp.mode=prod -XshowSettings:properties -version 2>&1 | grep 'app\.'
app.mode = prod
app.title = Hello World
-XshowSettings:properties は、JVM が持っているシステムプロパティを一覧で表示するオプションです。プログラムを書かずに値を確かめたいときに便利です。
| 書き方 | 結果 |
|---|---|
java -Dapp.mode=prod Main |
app.mode が prod になる |
java Main -Dapp.mode=prod |
args に入る。プロパティは null |
java -Dapp.title="Hello World" Main |
空白を含む値を渡せる |
java -Dapp.debug Main |
値を省くと空文字になる(null ではない) |
java -Dapp.mode=a -Dapp.mode=b Main |
同じキーを重ねると、後ろの b になった |
最後の2行は、次のコマンドで確かめました。
java -Dapp.debug -Dapp.mode=a -Dapp.mode=b -XshowSettings:properties -version 2>&1 | grep 'app\.'
app.debug =
app.mode = b
OpenJDK 21.0.10 では、値を省いた app.debug は空文字に、同じキーを重ねた app.mode は後ろの b になりました。なお、値が空文字のとき Boolean.getBoolean("app.debug") は false を返しました。フラグとして使うなら -Dapp.debug=true と値まで書きます。
環境変数から -D を渡す:JAVA_TOOL_OPTIONS
起動コマンドを書き換えられない場面では、環境変数 JAVA_TOOL_OPTIONS に -D を書いておくと、JVM が起動時に読み取ります。
JAVA_TOOL_OPTIONS="-Dapp.mode=tool" java EnvBasic.java
Picked up JAVA_TOOL_OPTIONS: -Dapp.mode=tool
app.mode (prop) = tool
1行目の「Picked up JAVA_TOOL_OPTIONS: …」は、JVM が標準エラーに出すお知らせです。コマンドラインでも同じキーを指定した場合は、コマンドラインの値が使われました。
JAVA_TOOL_OPTIONS="-Dapp.mode=tool" java -Dapp.mode=cli EnvBasic.java
Picked up JAVA_TOOL_OPTIONS: -Dapp.mode=tool
app.mode (prop) = cli
java コマンド専用の JDK_JAVA_OPTIONS でも同じことができます。
JDK_JAVA_OPTIONS="-Dapp.mode=jdkopt" java EnvBasic.java
NOTE: Picked up JDK_JAVA_OPTIONS: -Dapp.mode=jdkopt
app.mode (prop) = jdkopt
「環境変数で指定したつもりなのにプロパティが変わる」「覚えのない Picked up の行が出る」ときは、この2つの環境変数を確かめると原因が見つかります。
主要なシステムプロパティ一覧(実値つき)
JVM が最初から用意しているシステムプロパティのうち、よく使うものを実際の値つきで並べます。説明は Java 21 の API ドキュメント(System.getProperties)の記載に沿っています。
| キー | 意味 | この環境での値 |
|---|---|---|
java.version |
Java のバージョン | 21.0.10 |
java.version.date |
そのバージョンの日付 | 2026-01-20 |
java.vendor |
提供元 | Ubuntu |
java.home |
Java のインストール先 | /usr/lib/jvm/java-21-openjdk-amd64 |
java.vm.name |
JVM の名前 | OpenJDK 64-Bit Server VM |
java.specification.version |
Java 仕様のバージョン | 21 |
java.class.version |
クラスファイルのバージョン | 65.0 |
java.class.path |
クラスパス | .(-cp で指定した値) |
java.io.tmpdir |
一時ファイルの既定の場所 | /tmp |
os.name |
OS の名前 | Linux |
os.arch |
CPU アーキテクチャ | amd64 |
os.version |
OS のバージョン | 6.18.44-fc-v64(カーネルの版) |
user.name |
ユーザーのアカウント名 | taro |
user.home |
ホームディレクトリ | /home/taro |
user.dir |
起動時の作業ディレクトリ | /home/taro/demo |
file.separator |
パスの区切り文字 | /(UNIX 系) |
path.separator |
パス一覧の区切り文字 | :(UNIX 系) |
line.separator |
改行文字 | \n(UNIX 系。長さ1) |
file.encoding |
既定の文字コード | UTF-8 |
native.encoding |
OS・ロケール由来の文字コード | UTF-8(ロケールが C.UTF-8 のとき) |
stdout.encoding |
System.out の文字コード | UTF-8(ロケールが C.UTF-8 のとき) |
stderr.encoding |
System.err の文字コード | UTF-8(ロケールが C.UTF-8 のとき) |
user.language |
言語 | en |
sun.java.command |
起動したクラスと引数(OpenJDK の実装が設定) | 実行したコマンドによる |
一覧は次のプログラムで出力しました。改行のような見えない文字は、エスケープして表示しています。
public class PropList {
public static void main(String[] args) {
String[] keys = {
"java.version", "java.version.date", "java.vendor", "java.home",
"java.vm.name", "java.specification.version", "java.class.version", "java.class.path",
"java.io.tmpdir", "os.name", "os.arch", "os.version",
"user.name", "user.home", "user.dir",
"file.separator", "path.separator", "line.separator",
"file.encoding", "native.encoding", "stdout.encoding", "stderr.encoding",
"user.language"
};
for (String k : keys) {
String v = System.getProperty(k);
// 改行などの見えない文字はエスケープして表示する
String shown = (v == null) ? "null"
: "\"" + v.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r") + "\"";
System.out.printf("%-28s = %s%n", k, shown);
}
System.out.println("プロパティの総数: " + System.getProperties().size());
}
}
java.version = "21.0.10"
java.version.date = "2026-01-20"
java.vendor = "Ubuntu"
java.home = "/usr/lib/jvm/java-21-openjdk-amd64"
java.vm.name = "OpenJDK 64-Bit Server VM"
java.specification.version = "21"
java.class.version = "65.0"
java.class.path = "."
java.io.tmpdir = "/tmp"
os.name = "Linux"
os.arch = "amd64"
os.version = "6.18.44-fc-v64"
user.name = "taro"
user.home = "/home/taro"
user.dir = "/home/taro/demo"
file.separator = "/"
path.separator = ":"
line.separator = "\n"
file.encoding = "UTF-8"
native.encoding = "UTF-8"
stdout.encoding = "UTF-8"
stderr.encoding = "UTF-8"
user.language = "en"
プロパティの総数: 48
用途別の逆引き
| 知りたいこと | 使うプロパティ | 代わりに使える API |
|---|---|---|
| いまの作業ディレクトリ | user.dir |
Path.of("").toAbsolutePath() |
| ホームディレクトリ | user.home |
― |
| 一時ファイルの置き場所 | java.io.tmpdir |
Files.createTempFile(...) |
| 改行文字 | line.separator |
System.lineSeparator() |
| パスの区切り | file.separator |
File.separator、Path.of(a, b) |
| Java のバージョン | java.version |
Runtime.version() |
| OS の判定 | os.name |
― |
| 既定の文字コード | file.encoding |
Charset.defaultCharset() |
パスを組み立てるときは、file.separator を文字列でつなぐよりも Path.of("data", "in.csv") のように API に任せるほうが、OS の違いを意識せずに書けます。
システムプロパティは実行中に変更できる
システムプロパティは System.setProperty で変更でき、System.clearProperty で削除できます。
public class PropSet {
public static void main(String[] args) {
System.out.println("変更前: " + System.getProperty("app.mode"));
String before = System.setProperty("app.mode", "test"); // 戻り値は直前の値
System.out.println("setProperty の戻り値: " + before);
System.out.println("変更後: " + System.getProperty("app.mode"));
System.clearProperty("app.mode");
System.out.println("clear後: " + System.getProperty("app.mode"));
// user.dir を書き換えても、実際の作業ディレクトリは変わらない
System.setProperty("user.dir", "/tmp");
System.out.println("user.dir = " + System.getProperty("user.dir"));
System.out.println("Path.of(\"\") = " + java.nio.file.Path.of("").toAbsolutePath());
}
}
java -Dapp.mode=prod PropSet.java の実行結果です。
変更前: prod
setProperty の戻り値: prod
変更後: test
clear後: null
user.dir = /tmp
Path.of("") = /home/taro/demo
最後の2行に注目してください。user.dir の値を書き換えても、実際の作業ディレクトリは移動しません。 プロパティの文字列が変わるだけで、Path の相対パスは起動時のディレクトリを基準に解決されました。JVM が用意したプロパティは「読むためのもの」と考え、自分で定義したキー(app. で始まるものなど)だけを変更するのが分かりやすい使い方です。
環境変数を全部取得する|getenv() の Map
引数なしの System.getenv() は、すべての環境変数を Map で返します。
import java.util.Map;
import java.util.TreeMap;
public class EnvAll {
public static void main(String[] args) {
Map<String, String> env = System.getenv();
System.out.println("件数: " + env.size());
// 名前順に並べ、APP_ で始まるものだけ表示
new TreeMap<>(env).forEach((k, v) -> {
if (k.startsWith("APP_")) System.out.println(k + "=" + v);
});
System.out.println("クラス: " + env.getClass().getName());
}
}
APP_MODE=dev APP_PORT=9000 java EnvAll.java の実行結果です(件数は環境によって変わります)。
件数: 194
APP_MODE=dev
APP_PORT=9000
クラス: java.util.Collections$UnmodifiableMap
Map の順序は決まっていないので、一覧で出すときは TreeMap に入れて名前順にすると読みやすくなります。全件をそのままログに出すと、トークンやパスワードが入った環境変数まで記録されてしまうので、表示するのは必要な名前だけに絞るのがおすすめです。
getenv の Map に書き込むと UnsupportedOperationException
返ってくる Map は、クラス名のとおり変更できない Mapです。put してみます。
import java.util.Map;
public class EnvPut {
public static void main(String[] args) {
Map<String, String> env = System.getenv();
env.put("APP_MODE", "test"); // 書き込もうとすると…
System.out.println(env.get("APP_MODE"));
}
}
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.Collections$UnmodifiableMap.put(Collections.java:1660)
at EnvPut.main(EnvPut.java:6)
Java には、自分のプロセスの環境変数を書き換える API がありません。 値を切り替えたいときの方法は次の3つです。
| やりたいこと | 方法 |
|---|---|
| 起動時に値を変えたい | シェルで export する、または NAME=値 java ... で起動する |
| 実行中に切り替えられる設定がほしい | システムプロパティ(System.setProperty)や、自分の設定クラスを使う |
| 子プロセスにだけ別の値を渡したい | ProcessBuilder.environment() に put する(次の章) |
大文字・小文字と空文字
public class Case {
public static void main(String[] args) {
System.out.println(System.getenv("APP_MODE") + " / " + System.getenv("app_mode") + " / " + System.getenv("App_Mode"));
System.out.println("[" + System.getenv("APP_EMPTY") + "] length=" + System.getenv("APP_EMPTY").length());
System.out.println("lineSeparator length=" + System.lineSeparator().length());
}
}
app_mode=lower APP_MODE=UPPER APP_EMPTY= java Case.java の実行結果です。
UPPER / lower / null
[] length=0
lineSeparator length=1
Linux では APP_MODE と app_mode は別の変数として扱われました。API ドキュメントには、UNIX 系では通常大文字と小文字を区別し、Windows では通常区別しない、と書かれています。また、APP_EMPTY= のように値を空にして設定した変数は、null ではなく空文字で返ります。「設定されていない」と「空で設定されている」は別の状態です。
null への備え|Optional で既定値を付ける
設定されていない環境変数は null なので、そのままメソッドを呼ぶと NullPointerException になります。
public class Npe {
public static void main(String[] args) {
String mode = System.getenv("APP_MODE"); // 未設定なら null
System.out.println(mode.toUpperCase());
}
}
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because "<local1>" is null
at Npe.main(Npe.java:4)
既定値の付け方を、用途別にまとめたコードです。
import java.util.Optional;
public class Defaults {
/** 環境変数 → システムプロパティ → 既定値 の順に探す */
static String config(String envName, String propName, String defaultValue) {
return Optional.ofNullable(System.getenv(envName))
.filter(s -> !s.isBlank()) // 空文字は設定なしと同じ扱いにする
.or(() -> Optional.ofNullable(System.getProperty(propName)))
.orElse(defaultValue);
}
public static void main(String[] args) {
// 1) Optional で既定値
String mode = Optional.ofNullable(System.getenv("APP_MODE")).orElse("dev");
System.out.println("APP_MODE = " + mode);
// 2) 数値に変換して既定値
int port = Optional.ofNullable(System.getenv("APP_PORT"))
.map(Integer::parseInt)
.orElse(8080);
System.out.println("APP_PORT = " + port);
// 3) JDK 標準の getOrDefault(Map として扱う)
System.out.println("APP_NAME = " + System.getenv().getOrDefault("APP_NAME", "sample"));
// 4) システムプロパティは第2引数で既定値
System.out.println("app.mode = " + System.getProperty("app.mode", "dev"));
// 5) 数値・真偽値のプロパティ用メソッド
System.out.println("app.port = " + Integer.getInteger("app.port", 8080));
System.out.println("app.debug = " + Boolean.getBoolean("app.debug"));
// 6) 環境変数 → プロパティ → 既定値
System.out.println("config = " + config("APP_MODE", "app.mode", "dev"));
// 7) 必須の設定は、無ければ早めに知らせる
try {
String url = Optional.ofNullable(System.getenv("APP_DB_URL"))
.orElseThrow(() -> new IllegalStateException("環境変数 APP_DB_URL を設定してください"));
System.out.println(url);
} catch (IllegalStateException e) {
System.out.println("例外: " + e.getMessage());
}
}
}
何も設定せずに java Defaults.java を実行した結果です。
APP_MODE = dev
APP_PORT = 8080
APP_NAME = sample
app.mode = dev
app.port = 8080
app.debug = false
config = dev
例外: 環境変数 APP_DB_URL を設定してください
値を設定して実行します。
APP_MODE=stg APP_PORT=9001 APP_NAME=shop APP_DB_URL=jdbc:postgresql://localhost/app \
java -Dapp.mode=prod -Dapp.port=9002 -Dapp.debug=true Defaults.java
APP_MODE = stg
APP_PORT = 9001
APP_NAME = shop
app.mode = prod
app.port = 9002
app.debug = true
config = stg
jdbc:postgresql://localhost/app
| 書き方 | 向いている場面 |
|---|---|
Optional.ofNullable(System.getenv(x)).orElse(既定値) |
文字列の環境変数 |
.map(Integer::parseInt).orElse(8080) |
数値に変換したい環境変数 |
System.getenv().getOrDefault(x, 既定値) |
短く書きたいとき |
System.getProperty(key, 既定値) |
システムプロパティ |
Integer.getInteger(key, 既定値) |
数値のシステムプロパティ |
Boolean.getBoolean(key) |
真偽値のシステムプロパティ("true" のときだけ true) |
.orElseThrow(...) |
必須の設定。無いことを起動時に知らせる |
数値への変換では、数字以外が入っていた場合も考えておきます。APP_PORT=abc java Defaults.java を実行すると、次の例外になりました。
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:662)
設定値の読み込みはアプリケーションの起動時に1か所へまとめ、不正な値はその場でメッセージを出して止めると、原因がすぐ分かります。
子プロセスに環境変数を渡す|ProcessBuilder
Java から別のコマンドを起動するときは、ProcessBuilder の environment() で、子プロセスに渡す環境変数を変更できます。こちらの Map は書き込めます。
import java.util.Map;
public class Child {
public static void main(String[] args) throws Exception {
// sh -c で、子プロセス側の環境変数を表示する
ProcessBuilder pb = new ProcessBuilder("sh", "-c",
"echo \"APP_MODE=$APP_MODE / APP_TOKEN=$APP_TOKEN / LANG=${LANG:-unset}\"");
Map<String, String> env = pb.environment(); // 親の環境変数のコピー(書き込める)
env.put("APP_MODE", "child"); // 追加・上書き
env.put("APP_TOKEN", "abc123");
env.remove("LANG"); // 子には渡さない
pb.inheritIO(); // 子の出力をそのまま表示
int code = pb.start().waitFor();
System.out.println("exit = " + code);
// 親(この Java プロセス)の環境変数は変わらない
System.out.println("親の APP_MODE = " + System.getenv("APP_MODE"));
System.out.println("親の APP_TOKEN = " + System.getenv("APP_TOKEN"));
// 子の Java にシステムプロパティを渡すときは -D を引数に並べる
new ProcessBuilder("java", "-Dapp.mode=child", "-XshowSettings:properties", "-version")
.redirectErrorStream(true)
.start().inputReader().lines()
.filter(l -> l.contains("app.mode"))
.forEach(System.out::println);
}
}
APP_MODE=parent LANG=C.UTF-8 java Child.java の実行結果です。
APP_MODE=child / APP_TOKEN=abc123 / LANG=unset
exit = 0
親の APP_MODE = parent
親の APP_TOKEN = null
app.mode = child
- environment() は、親の環境変数のコピーから始まります。 put で追加・上書き、remove で削除できます。
- 変更は子プロセスにだけ反映されます。 親の APP_MODE は parent のままで、APP_TOKEN も親には存在しません。
- システムプロパティは子に引き継がれません。 子も Java なら、-D を引数として並べて渡します。
API ドキュメントには、Java の子プロセスへ情報を渡すときは、環境変数よりもシステムプロパティが一般に好ましい、と書かれています。環境変数は子孫のプロセス全体に見えるため影響範囲が広い、というのが理由です。
file.encoding と native.encoding の違い(JDK 18 以降)
文字コードに関するプロパティは、JDK 18 で意味が変わりました。JEP 400(UTF-8 by Default)で、標準 API の既定の文字コードが、OS やロケールに関係なく UTF-8 になったためです。関連するプロパティを整理します(JEP 400 と Java 21 の API ドキュメントの記載です)。
| プロパティ | 意味 | 指定できる値 |
|---|---|---|
file.encoding |
既定の文字コード(Charset.defaultCharset())。JDK 18 以降の既定は UTF-8 | コマンドラインで UTF-8 か COMPAT |
native.encoding |
OS・ロケールから決まる文字コード。JDK 17 で追加 | 設定しても効果なし(読むためのもの) |
stdout.encoding |
System.out の文字コード | 起動時に UTF-8 を指定できる |
stderr.encoding |
System.err の文字コード | 起動時に UTF-8 を指定できる |
-Dfile.encoding=COMPAT を付けると、JDK 17 以前と同じ「OS・ロケールから決める」動きに戻ります。ロケールを変えながら確かめます。
import java.nio.charset.Charset;
public class Enc {
public static void main(String[] args) {
System.out.println("file.encoding = " + System.getProperty("file.encoding"));
System.out.println("native.encoding = " + System.getProperty("native.encoding"));
System.out.println("stdout.encoding = " + System.getProperty("stdout.encoding"));
System.out.println("defaultCharset() = " + Charset.defaultCharset());
System.out.println("System.out = " + System.out.charset());
System.out.println("日本語の表示");
}
}
ロケールが C.UTF-8 のとき
LC_ALL=C.UTF-8 java Enc.java
file.encoding = UTF-8
native.encoding = UTF-8
stdout.encoding = UTF-8
defaultCharset() = UTF-8
System.out = UTF-8
日本語の表示
ロケールが C(UTF-8 ではない)のとき
LANG=C java Enc.java
file.encoding = UTF-8
native.encoding = ANSI_X3.4-1968
stdout.encoding = ANSI_X3.4-1968
defaultCharset() = UTF-8
System.out = US-ASCII
??????
file.encoding は UTF-8 のままですが、native.encoding はロケール由来の ANSI_X3.4-1968(ASCII)になり、System.out もそれに従います。 そのため、ファイルの読み書きは UTF-8 で行われる一方、画面への日本語の表示は「?」になりました。コンテナや cron のようにロケールが設定されていない場所で日本語のログが「?」になるときは、この状態です。
表示を直す2つの方法
1つ目は、ロケールを UTF-8 のものにする方法(上の C.UTF-8 の例)です。2つ目は、System.out の文字コードを起動時に指定する方法です。
LANG=C java -Dstdout.encoding=UTF-8 Enc.java
file.encoding = UTF-8
native.encoding = ANSI_X3.4-1968
stdout.encoding = UTF-8
defaultCharset() = UTF-8
System.out = UTF-8
日本語の表示
COMPAT を指定したとき
LANG=C java -Dfile.encoding=COMPAT Enc.java
file.encoding = ANSI_X3.4-1968
native.encoding = ANSI_X3.4-1968
stdout.encoding = ANSI_X3.4-1968
defaultCharset() = US-ASCII
System.out = US-ASCII
??????????????????
COMPAT では、file.encoding が native.encoding の値に置き換わり、既定の文字コードもロケール由来になりました。4つの実行結果をまとめます。
| 起動方法 | file.encoding | native.encoding | System.out | 日本語の表示 |
|---|---|---|---|---|
| ロケール C.UTF-8 | UTF-8 | UTF-8 | UTF-8 | 表示される |
| ロケール C | UTF-8 | ANSI_X3.4-1968 | US-ASCII | ? になる |
ロケール C + -Dstdout.encoding=UTF-8 |
UTF-8 | ANSI_X3.4-1968 | UTF-8 | 表示される |
ロケール C + -Dfile.encoding=COMPAT |
ANSI_X3.4-1968 | ANSI_X3.4-1968 | US-ASCII | ? になる |
覚えておきたいのは、file.encoding は「ファイルなどを読み書きする標準 API の既定」、native.encoding は「OS 側の文字コード」、画面への出力は stdout.encoding という役割分担です。なお、ファイルの読み書きでは Files.readString(path, StandardCharsets.UTF_8) のように文字コードを明示しておくと、どの設定でも同じ結果になります。
環境変数とシステムプロパティの使い分け
| 場面 | 向いているもの | 理由 |
|---|---|---|
| コンテナや CI など、外から設定を差し込む | 環境変数 | 起動コマンドを変えずに値を切り替えられる |
| PATH や HOME など OS が決めた値を読む | 環境変数 | OS との共通の約束ごと |
| Java のプログラムだけに関係する設定 | システムプロパティ | その JVM の中だけに見える。既定値も書きやすい |
| テストで一時的に値を変えたい | システムプロパティ | setProperty と clearProperty で切り替えられる |
| 子の Java プロセスに設定を渡す | システムプロパティ(-D) | API ドキュメントが一般に好ましいとしている |
| Java のバージョンや OS を調べる | システムプロパティ | JVM が用意している |
両方に対応したいときは、Defaults の例にある config メソッドのように「環境変数 → システムプロパティ → 既定値」の順に探す形にしておくと、どちらで指定しても動きます。
よくある質問
Q. System.getenv と System.getProperty の違いは何ですか? A. getenv は OS の環境変数(export で設定するもの)、getProperty は JVM のシステムプロパティ(-D で設定するものと、java.version など JVM が用意するもの)を読みます。別の入れ物なので、片方に設定した値はもう片方からは取れません。
Q. Java のプログラムの中で環境変数を設定できますか? A. 自分のプロセスの環境変数は変更できません。System.getenv() の Map に put すると UnsupportedOperationException になります。実行中に切り替えたい設定はシステムプロパティに、子プロセスに渡したい値は ProcessBuilder の environment() に設定します。
Q. export したのに getenv が null を返します。 A. export したシェルと、Java を起動したシェルやツールが別になっていることが多いです。IDE やサービスとして起動している場合は、その起動設定に環境変数を書きます。System.getenv() を全件出力して、名前(大文字・小文字を含む)が合っているかを確かめるのが近道です。
Q. システムプロパティを全部見るにはどうすればよいですか?
A. プログラムなしで見るなら java -XshowSettings:properties -version が手軽です。プログラムの中では System.getProperties() を使います。
まとめ
- 環境変数は System.getenv("名前")、システムプロパティは System.getProperty("キー")。 無いときはどちらも null
- export した値は getenv、-D で渡した値は getProperty で取れる。 -D はクラス名より前に書く
- システムプロパティは第2引数で既定値を指定できる。 環境変数は Optional や getOrDefault で既定値を付ける
- getenv() の Map は読み取り専用。 put すると UnsupportedOperationException になる
- 子プロセスに渡す環境変数は ProcessBuilder の environment() で変更する。 親の環境変数は変わらない
- user.dir・user.home・java.version・os.name・java.io.tmpdir・line.separator は JVM が用意するプロパティ
- JDK 18 以降、file.encoding の既定は UTF-8。 OS 側の文字コードは native.encoding、画面出力は stdout.encoding で決まる
参考・出典
- Java SE 21 API ドキュメント:System(getenv / getProperty / getProperties)、ProcessBuilder(docs.oracle.com)
- JEP 400: UTF-8 by Default(openjdk.org)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java 環境変数の取得方法|System.getenvとシステムプロパティ一覧」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。