【Java】NullPointerExceptionの原因と防ぎ方を初心者向けに解説

Java

こんにちは、かつコーチです。
Javaを書いていて一度は必ず遭遇するのがNullPointerExceptionではないでしょうか。
通称「ぬるぽ」とも呼ばれるこのエラーは初心者だけでなく実務でも頻発します。今回は発生の仕組みと防ぎ方を整理します。

NullPointerExceptionとは?何もない場所を触ろうとするエラー

nullが指す意味

nullとは、変数がどのオブジェクトも参照していない状態を表す特別な値です。
NullPointerException(略称NPE)は、nullのオブジェクトに対してメソッド呼び出しやフィールドアクセスを行おうとしたときに発生します。

String name = null;
System.out.println(name.length()); // ここでNPEが発生
Exception in thread "main" java.lang.NullPointerException:
    Cannot invoke "String.length()" because "name" is null
        at Main.main(Main.java:3)

Java 14以降は「詳細なNPEメッセージ」が標準で有効になっており、because "name" is nullのようにどの変数がnullだったかまで表示してくれます。
Java 21でもこの機能は健在なので、まずメッセージのbecause以降をしっかり読む習慣をつけましょう。

NullPointerExceptionが起きやすい3つの場面

メソッドの戻り値がnullの場合

外部から取得したデータが見つからない場合にnullを返すメソッド設計はよくあります。

// ❌Before:戻り値がnullかもしれないことを考慮していない
Map<String, String> users = new HashMap<>();
String email = users.get("katsu"); // 存在しないキーなのでnullが返る
System.out.println(email.toUpperCase()); // NPE発生
Exception in thread "main" java.lang.NullPointerException:
    Cannot invoke "String.toUpperCase()" because "email" is null
        at Main.main(Main.java:4)
// ✅After:null チェックを行ってから使う
String email = users.get("katsu");
if (email != null) {
    System.out.println(email.toUpperCase());
} else {
    System.out.println("メールアドレスが未登録です");
}

Map.get()は該当キーがない場合nullを返す仕様のため、使う側で必ずチェックが必要です。

配列・コレクションの要素がnullの場合

配列は生成直後、参照型の要素はすべてnullで初期化されます。

// ❌Before:初期化前の配列要素にアクセス
String[] names = new String[3];
System.out.println(names[0].length()); // 各要素はまだnull
Exception in thread "main" java.lang.NullPointerException:
    Cannot invoke "String.length()" because "<local1>[0]" is null
        at Main.main(Main.java:2)
// ✅After:値を設定してから使う
String[] names = new String[3];
names[0] = "かつコーチ";
System.out.println(names[0].length());

メソッドチェーンの途中でnullが挟まる場合

getA().getB().getC()のようなメソッドチェーンは、途中のどこかがnullだとまとめて落ちます。

// ❌Before:どこがnullか分かりにくいチェーン呼び出し
String city = user.getAddress().getCity();
Exception in thread "main" java.lang.NullPointerException:
    Cannot invoke "Address.getCity()" because the return value of
    "User.getAddress()" is null
        at Main.main(Main.java:1)

このように詳細メッセージなら「getAddress()の戻り値がnullだった」と特定できますが、根本対策としてはOptionalの活用が有効です。

// ✅After:Optionalでnullを安全に扱う
String city = Optional.ofNullable(user.getAddress())
        .map(Address::getCity)
        .orElse("不明");

Optionalの詳しい使い方は別記事で扱っていますが、チェーンが深くなる設計自体を見直すきっかけにもなります。

つまずいたポイント:早期リターンで防いだ話

実務で、フォーム入力を処理するメソッドが本番環境でだけ突然落ちるという不具合に遭遇したことがあります。
ログに残っていたのは次のようなスタックトレースでした。

java.lang.NullPointerException:
    Cannot invoke "String.trim()" because "request.getParameter(\"email\")" is null
        at com.example.FormService.validate(FormService.java:12)

原因は、フォームのemailパラメータが未入力のリクエストを想定しておらず、trim()を無条件に呼んでいたことでした。

// ❌Before:パラメータの存在チェックなし
public void validate(HttpServletRequest request) {
    String email = request.getParameter("email").trim();
    // ...
}
// ✅After:早期リターンでnullを弾く
public void validate(HttpServletRequest request) {
    String email = request.getParameter("email");
    if (email == null || email.isBlank()) {
        throw new IllegalArgumentException("メールアドレスは必須です");
    }
    email = email.trim();
    // ...
}

早期リターン(条件を満たさない場合にメソッドの先頭で処理を打ち切る書き方)を使うことで、後続処理を安心してnullを気にせず書けるようになります。

まとめ

この記事のポイント

  • NPEはnullに対してメソッド呼び出しやフィールドアクセスをしたときに発生する
  • Java 14以降の詳細メッセージはbecause以降にnullだった変数名が表示される
  • Map.get()の戻り値・初期化前の配列要素・メソッドチェーンの3パターンで特によく発生する
  • 外部からの入力値は早期リターンでnullチェックしてから使うのが安全

次に読むべき記事

  • nullをより構造的に扱う方法は「Optionalでnullを安全に扱う」の記事へ
  • コンパイル時点のエラーは「よくあるコンパイルエラーの読み方」の記事へ

タグ: Java, 初心者向け, エラー解決

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