Java Tips | 基本ユーティリティ:環境変数取得

Java Java
スポンサーリンク

環境変数取得は「インフラ側の設定をアプリから読む」技

業務システムでは、「本番とステージングで接続先を変えたい」「パスワードや API キーをコードに書きたくない」「コンテナやクラウドの設定をそのまま使いたい」といったニーズが必ず出てきます。
こういう「インフラ側で決める設定」を、アプリケーションから読むための代表的な仕組みが「環境変数」です。

Java では System.getenv(...) で環境変数を取得できますが、そのままベタ書きすると「キー名の typo」「null の扱い」「デフォルト値」「型変換」がバラバラになりがちです。
そこで、「環境変数取得」をユーティリティとしてまとめておくと、実務でかなり扱いやすくなります。


環境変数とシステムプロパティの違いをまず整理する

環境変数は「OS/インフラ側の設定」

環境変数は、OS やコンテナ、クラウドの設定として定義される値です。
Linux なら export APP_ENV=prod、Docker なら -e APP_ENV=prod、Kubernetes なら env: セクションで設定する、あの値です。

Java からはこう取得します。

String env = System.getenv("APP_ENV");
String dbUrl = System.getenv("DB_URL");
Java

ここで重要なのは、「アプリケーションの外側で決められる」という点です。
コードを書き換えなくても、デプロイ時の設定だけで挙動を変えられるので、12-factor app 的な設計とも相性が良いです。

システムプロパティとの役割分担

システムプロパティ(System.getProperty("app.env"))は JVM 起動オプションや Java 内部で完結する設定に向いています。
一方、環境変数は OS/コンテナ/クラウド側で管理される設定に向いています。

現場では、例えばこう分けることが多いです。
接続先 URL やパスワード、API キーなど「インフラが管理したいもの」→環境変数。
Java のチューニングやアプリ固有の細かいフラグなど「JVM 起動時に渡したいもの」→システムプロパティ。

どちらを使うかはプロジェクトの方針次第ですが、「環境変数はインフラの領域」という感覚を持っておくと整理しやすくなります。


System.getenv の基本と、そのまま使うとつらいポイント

素朴な取得方法

一番シンプルなコードはこうです。

public class EnvBasic {

    public static void main(String[] args) {
        String appEnv = System.getenv("APP_ENV");
        String dbUrl  = System.getenv("DB_URL");

        System.out.println("APP_ENV = " + appEnv);
        System.out.println("DB_URL  = " + dbUrl);
    }
}
Java

キーが存在しない場合、System.getenvnull を返します。
ここで初心者がよくハマるのが、「環境変数が設定されていないのに、そのまま使ってしまって NullPointerException」パターンです。

また、環境変数はすべて文字列なので、「真偽値」「数値」として使いたい場合は自分でパースする必要があります。
これを呼び出し側で毎回やっていると、コードが散らかり、挙動もバラバラになります。


実務で使える「環境変数取得ユーティリティ」の最小形

null とデフォルト値、型変換を一箇所に集約する

まずは、よく使うパターンをユーティリティに閉じ込めます。

public final class Envs {

    private Envs() {}

    public static String get(String key) {
        return System.getenv(key);
    }

    public static String getOrDefault(String key, String defaultValue) {
        String value = System.getenv(key);
        return (value != null) ? value : defaultValue;
    }

    public static boolean getBoolean(String key, boolean defaultValue) {
        String value = System.getenv(key);
        if (value == null) {
            return defaultValue;
        }
        return Boolean.parseBoolean(value);
    }

    public static int getInt(String key, int defaultValue) {
        String value = System.getenv(key);
        if (value == null) {
            return defaultValue;
        }
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }
}
Java

使う側はこう書けます。

String appEnv = Envs.getOrDefault("APP_ENV", "dev");
boolean featureX = Envs.getBoolean("FEATURE_X_ENABLED", false);
int maxConnections = Envs.getInt("DB_MAX_CONNECTIONS", 10);
Java

ここで深掘りしたいポイントは三つです。

一つ目は、「デフォルト値の扱いをユーティリティ側に寄せている」ことです。
「設定がなければ dev」「設定がなければ false」「設定がなければ 10」といったポリシーを、呼び出し側ではなくメソッド呼び出しで明示できます。

二つ目は、「文字列→boolean/int の変換を一箇所に集約している」ことです。
Boolean.parseBooleanInteger.parseInt をあちこちに書くより、getBooleangetInt に閉じ込めたほうが読みやすく、バグも減ります。

三つ目は、「パース失敗時の扱い(NumberFormatException のときはデフォルト値に戻す)」を統一していることです。
ここを「例外を投げる」にするか「デフォルトに戻す」にするかも、ユーティリティ側で方針を決めておくと、アプリ全体で一貫性が出ます。


「必須環境変数」と「任意環境変数」を分けて扱う

必須環境変数は「なければ起動時に落とす」

例えば、「DB_URL が設定されていない状態でアプリが動いても意味がない」といったケースがあります。
こういう「必須環境変数」は、起動時にチェックして、なければ即エラーにしたほうが健全です。

public static String getRequired(String key) {
    String value = System.getenv(key);
    if (value == null || value.isBlank()) {
        throw new IllegalStateException("Required environment variable is missing: " + key);
    }
    return value;
}
Java

使う側はこうです。

String dbUrl = Envs.getRequired("DB_URL");
Java

ここでの重要ポイントは、「必須かどうかを if 文ではなくメソッド名で表現している」ことです。
getRequired と書かれていれば、「ここで設定がなければ起動時に落ちる」という意図が一目で分かります。

任意環境変数は「デフォルト付き」で扱う

一方、「あれば使うけど、なければデフォルトでいい」という任意環境変数は、getOrDefaultgetBooleangetInt で十分です。
この「必須/任意」の切り分けを、ユーティリティのメソッドレベルで表現しておくと、設定の意味がコードから読み取りやすくなります。


よく使う環境変数キーを定数としてまとめる

マジックストリングを減らす

"APP_ENV""DB_URL" のようなキー文字列を、コードのあちこちにベタ書きすると、typo や変更漏れの原因になります。
そこで、「環境変数のキーを集約したクラス」を作るのが定番です。

public final class EnvKeys {

    private EnvKeys() {}

    public static final String APP_ENV = "APP_ENV";
    public static final String DB_URL = "DB_URL";
    public static final String DB_MAX_CONNECTIONS = "DB_MAX_CONNECTIONS";
    public static final String FEATURE_X_ENABLED = "FEATURE_X_ENABLED";
}
Java

使う側はこうなります。

String env = Envs.getOrDefault(EnvKeys.APP_ENV, "dev");
String dbUrl = Envs.getRequired(EnvKeys.DB_URL);
int maxConnections = Envs.getInt(EnvKeys.DB_MAX_CONNECTIONS, 10);
Java

ここで深掘りしたいのは、「キー名を“意味のある定数名”にすることで、設定の意図がコードから伝わる」ことです。
"DB_MAX_CONNECTIONS" だけ見ても何のことか分かりにくいですが、EnvKeys.DB_MAX_CONNECTIONS なら「DB の最大接続数だな」とすぐに分かります。


起動時に環境変数をログに出しておく実務テク

「どんな設定で動いていたか」を後から追えるようにする

本番でトラブルが起きたとき、「このプロセスはどんな環境変数で動いていたのか」が分からないと、原因調査が難しくなります。
そこで、アプリ起動時に「重要な環境変数」をまとめてログに出しておくのは、実務でよく効くテクニックです。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class EnvLogger {

    private static final Logger log = LoggerFactory.getLogger(EnvLogger.class);

    private EnvLogger() {}

    public static void logStartupEnv() {
        log.info("Environment at startup: APP_ENV={}, DB_URL={}, DB_MAX_CONNECTIONS={}, FEATURE_X_ENABLED={}",
                Envs.getOrDefault(EnvKeys.APP_ENV, "dev"),
                Envs.getOrDefault(EnvKeys.DB_URL, "(not set)"),
                Envs.getInt(EnvKeys.DB_MAX_CONNECTIONS, 10),
                Envs.getBoolean(EnvKeys.FEATURE_X_ENABLED, false)
        );
    }
}
Java

起動時に一度だけこう呼びます。

EnvLogger.logStartupEnv();
Java

これで、「どの環境で」「どんな設定値で」動いていたかがログから追えるようになります。
ここでのポイントは、「設定値を“使うときだけ”でなく、“起動時にまとめて記録しておく”」という発想です。


セキュリティと環境変数:パスワードをどう扱うか

環境変数には機密情報も載る

DB パスワードや API キー、トークンなどを環境変数で渡すことはよくあります。
これは「コードや設定ファイルに平文で書かない」という意味では安全側の選択ですが、
それをそのままログに出したり、画面に表示したりすると、情報漏えいのリスクになります。

ユーティリティ側で「マスクする」仕組みを用意しておくと安心です。

public static String masked(String value) {
    if (value == null || value.isEmpty()) {
        return "(empty)";
    }
    return "****";
}
Java

ログ出力時にこう使います。

String dbPassword = System.getenv("DB_PASSWORD");
log.info("DB_PASSWORD={}", Envs.masked(dbPassword));
Java

「どの環境変数はログに出してよいか」「どれはマスクすべきか」は、チームでルールを決めておくと安全です。


テストコードで環境変数をどう扱うか

Java から環境変数は基本的に書き換えられない

System.setProperty でシステムプロパティは上書きできますが、環境変数は Java から基本的に変更できません。
そのため、テストで環境変数に依存するコードを書くときは、設計を工夫する必要があります。

一つの考え方は、「環境変数取得ユーティリティをインターフェース化して、テスト時に差し替える」ことです。

public interface EnvProvider {
    String get(String key);
}

public class SystemEnvProvider implements EnvProvider {
    @Override
    public String get(String key) {
        return System.getenv(key);
    }
}
Java

アプリ側では EnvProvider をコンストラクタで受け取り、本番では SystemEnvProvider、テストでは「テスト用の実装(任意の値を返す)」を渡します。
こうしておくと、「環境変数に依存するロジック」をテストしやすくなります。


まとめ:環境変数取得ユーティリティで身につけるべき感覚

環境変数取得は、「ただ文字列を読む」だけではなく、「インフラ側の設定をどう設計し、どう安全にコードから扱うか」という話です。

押さえておきたいポイントは次の通りです。

System.getenv をそのままばらまかず、Envs のようなユーティリティに閉じ込める。
文字列だけでなく、boolean/int などへの変換と、パース失敗時の扱いを一箇所で決める。
「必須環境変数」と「任意環境変数」をメソッドレベルで分け、設定漏れは起動時に気づけるようにする。
キー文字列は EnvKeys のような定数クラスにまとめ、typo と意味不明なマジックストリングを減らす。
起動時に重要な環境変数をログに出しておきつつ、パスワードなど機密情報はマスクする。

タイトルとURLをコピーしました