- JAVA
- 技術 💻
NullPointerExceptionの原因と対処法|原因別早見表【Java】
java.lang.NullPointerException(NPE、通称「ぬるぽ」)は、null(何も指していない参照)に対してメソッドを呼んだり、フィールドや配列を読んだりしたときに出る例外です。Java 14 以降は、メッセージの because "○○" is null の ○○ が「null だった式」なので、まずそこを見ます。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.add(Object)" because "this.items" is null
at Npe$Cart.add(Npe.java:8)
at Npe.main(Npe.java:30)
この例では「this.items(フィールド items)が null だったので、List.add を呼べなかった。場所は Npe.java の8行目」と読めます。同じコードを JDK 8 で動かすと、メッセージは java.lang.NullPointerException だけです。 この記事では、よくある原因10パターンを JDK 8・11・17・21・25 で実際に動かし、出力を並べています。
原因別の早見表(メッセージ・典型コード・直し方)
メッセージは JDK 17・21・25 で同じ文面でした。自分のエラーに近い行を探すと、直し方が分かります。
| 原因 | 典型コード | Java 14 以降のメッセージ(抜粋) | 直し方 |
|---|---|---|---|
| ① 初期化していないフィールド | private List<String> items; → items.add(s) |
because "this.items" is null |
宣言時に = new ArrayList<>()、final にしてコンストラクタで代入 |
| ② Map.get で無いキー | int n = map.get("apple"); |
Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null |
map.getOrDefault("apple", 0) |
| ③ 配列の要素が null | new String[3] → names[0].length() |
because "names[0]" is null |
要素を代入してから使う。Arrays.fill |
| ④ オートアンボクシング | Integer price = null; int t = price + 100; |
Cannot invoke "java.lang.Integer.intValue()" because "price" is null |
基本型 int で持つ。null なら既定値を決める |
| ⑤ メソッドの戻り値が null | findUser(id).name() |
because the return value of "Npe.findUser(String)" is null |
戻り値を Optional にする。呼ぶ側で分岐 |
| ⑥ Spring の @Autowired が効かない | new UserService().nameOf(id) |
because "this.userRepository" is null |
new しない。コンストラクタインジェクション |
| ⑦ JPA のエンティティ・遅延ロード | order.getCustomer().getName() |
because the return value of "Order.getCustomer()" is null など |
関連が null になり得るか確かめる。equals はゲッターで比較 |
| ⑧ Stream の途中に null | .map(String::toUpperCase) |
メッセージなし(メソッド参照の場合) | filter(Objects::nonNull)。toMap の値の null に注意 |
| ⑨ equals の向き | status.equals("ACTIVE") |
Cannot invoke "String.equals(Object)" because "status" is null |
"ACTIVE".equals(status) か Objects.equals |
| ⑩ toString と String.valueOf | o.toString() |
Cannot invoke "Object.toString()" because "o" is null |
String.valueOf(o) は "null" を返す |
確認環境:OpenJDK 8u504・11.0.32.1・17.0.20.1・21.0.10・25.0.4.1(Ubuntu 24.04)で再現コードを javac -g でコンパイルして実行し、出力をそのまま載せています。⑥は Spring Framework 4.3.30(Ubuntu のパッケージ)と JDK 25、静的解析は SpotBugs 4.10.5 と JSpecify 1.0(JDK 21)で実行しました。Helpful NullPointerException の仕様は JEP 358、Spring の null 安全は Spring Framework リファレンス、JPA の遅延ロードは Hibernate ORM のドキュメント、IDE の機能は IntelliJ IDEA のヘルプで確かめました(確認日 2026年10月16日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「NullPointerExceptionの原因と対処法|原因別早見表【Java】」
NullPointerException のエラーメッセージの読み方
3か所を順に見る
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Npe$UserRepository.findById(String)" because "this.userRepository" is null
at Npe$UserService.nameOf(Npe.java:24)
at Npe.main(Npe.java:56)
| 順番 | 見る場所 | この例で分かること |
|---|---|---|
| 1 | because "○○" is null の ○○ |
null だったのは this.userRepository(フィールド) |
| 2 | Cannot invoke "△△" の △△ |
呼ぼうとしたのは UserRepository.findById(String) |
| 3 | 最初の at 行(自分のコード) |
起きた場所は Npe.java の24行目、UserService.nameOf の中 |
| 4 | その下の at 行 |
呼び出し元。Npe.main の56行目から呼ばれた |
直すのは「null を使った24行目」ではなく、「null のまま渡してきた場所」であることが多い点に注意します。この例では、userRepository に値を入れるはずの処理(DI)が動いていないことが原因です。
メッセージの種類(Java 14 以降)
JDK 25 で実行して出たメッセージです。前半で「何をしようとしたか」、後半で「何が null だったか」が分かります。
| メッセージの前半 | 何をしようとしたか | 例 |
|---|---|---|
Cannot invoke "..." |
メソッド呼び出し | Cannot invoke "String.length()" because "names[0]" is null |
Cannot read field "..." |
フィールドの読み取り | Cannot read field "count" because "b.next" is null |
Cannot assign field "..." |
フィールドへの代入 | Cannot assign field "count" because "box" is null |
Cannot load from ... array |
配列の要素の読み取り | Cannot load from int array because "arr" is null |
Cannot store to ... array |
配列の要素への代入 | Cannot store to int array because "arr" is null |
Cannot read the array length |
配列.length |
Cannot read the array length because "arr" is null |
Cannot enter synchronized block |
synchronized (x) |
Cannot enter synchronized block because "lock" is null |
Cannot throw exception |
throw x |
Cannot throw exception because "ex" is null |
後半の書き方は次の3種類です。
| 後半 | 意味 |
|---|---|
because "price" is null |
ローカル変数・引数 price が null |
because "this.items" is null |
フィールド items が null |
because the return value of "Npe.findUser(String)" is null |
メソッド findUser が null を返した |
JDK 8・11 と JDK 17・21・25 のメッセージの違い
同じコード(後述の Npe.java)を各 JDK でコンパイルして実行した結果です。
| JDK | ①未初期化フィールドのメッセージ |
|---|---|
| 8(1.8.0_504) | java.lang.NullPointerException(説明なし) |
| 11(11.0.32.1) | java.lang.NullPointerException(説明なし) |
| 17(17.0.20.1) | Cannot invoke "java.util.List.add(Object)" because "this.items" is null |
| 21(21.0.10) | 同上 |
| 25(25.0.4.1) | 同上 |
この機能は Helpful NullPointerExceptions(JEP 358)といい、Java 14 で入りました。JEP 358 では、Java 14 の時点では既定で無効(-XX:+ShowCodeDetailsInExceptionMessages で有効)とされています。Java 15 から既定で有効になり、JDK 17・21・25 ではオプションなしでメッセージが出ました。JDK 25 で -XX:-ShowCodeDetailsInExceptionMessages を付けると、JDK 8 と同じく説明のないメッセージに戻りました。JDK 8・11 でこのオプションを付けると Unrecognized VM option 'ShowCodeDetailsInExceptionMessages' で起動しません。
JDK 8・11 でメッセージに説明がないときは、行番号の行にある「ドット(.)の左側」と「配列の [ の左側」と「アンボクシングされる値」を1つずつ疑います。 1行に a.getB().getC() のような連鎖があると切り分けにくいため、変数に分けて書くと行番号で特定できます。手元の Java の版の確かめ方は、関連記事「Javaバージョン一覧【2026年】LTSのサポート期限とJava 27・28」で扱っています。
変数名ではなく <local4> と出るとき
javac に -g(デバッグ情報を付ける)を付けずにコンパイルすると、ローカル変数の名前がクラスファイルに残らず、番号で表示されます。JDK 25 での結果です。
| コンパイル | メッセージ |
|---|---|
javac -g |
Cannot read the array length because "scores" is null |
javac(-g なし) |
Cannot read the array length because "<local4>" is null |
フィールド(this.items)とメソッドの戻り値は、-g がなくても名前が出ました。JEP 358 にも、ローカル変数名はクラスファイルにローカル変数表があるときだけ表示され、ないときは <local i> や <parameter i> になると書かれています。Maven や Gradle の既定のビルドではデバッグ情報が付くので、多くの場合は変数名が出ます。
メッセージに説明が付かない NullPointerException
Helpful NullPointerExceptions の説明が付くのは、JVM が自分で投げた NPE だけです(JEP 358)。ライブラリのコードが Objects.requireNonNull などで投げた NPE は、JDK 25 でもメッセージなしでした。
| 起きた場所 | JDK 25 のメッセージ | 見るところ |
|---|---|---|
Optional.of(null) |
なし(at java.util.Objects.requireNonNull → at java.util.Optional.of) |
スタックトレースで最初に出てくる自分のコードの行 |
Collectors.toMap の値が null |
なし(at java.util.Objects.requireNonNull → Collectors.lambda$uniqKeysMapAccumulator$0) |
同上 |
Stream のメソッド参照(String::toUpperCase) |
なし。行番号は終端操作(collect)の行 |
ラムダ式に書き換えると変数名が出る(後述) |
Objects.requireNonNull(email, "email は必須です") |
email は必須です |
自分で付けたメッセージ |
何度も起きるとスタックトレースが消える(OmitStackTraceInFastThrow)
本番のログで java.lang.NullPointerException の1行だけで、at 行が1行もないことがあります。HotSpot(標準の JVM)は、同じ場所で何度も起きる例外を最適化して、スタックトレースのない例外を使い回します。同じ箇所で NPE を10万回起こして数えた結果です(数は実行のたびに変わります)。
public class FastThrow {
static int len(String s) { return s.length(); }
public static void main(String[] args) {
int withTrace = 0, noTrace = 0;
String last = null;
for (int i = 0; i < 200_000; i++) {
try {
len(i % 2 == 0 ? "abc" : null);
} catch (NullPointerException e) {
if (e.getStackTrace().length == 0) { noTrace++; last = String.valueOf(e.getMessage()); }
else withTrace++;
}
}
System.out.println("スタックトレースあり=" + withTrace + " なし=" + noTrace + " 最後のメッセージ=" + last);
}
}
| JDK | 既定 | OmitStackTraceInFastThrow を無効にした場合 |
|---|---|---|
| 8 | あり=46593 なし=53407 | あり=100000 なし=0 |
| 17 | あり=45279 なし=54721 | あり=100000 なし=0 |
| 21 | あり=73070 なし=26930 | あり=100000 なし=0 |
| 25 | あり=49043 なし=50957 | あり=100000 なし=0 |
スタックトレースのない NPE は、メッセージも null でした(JDK 25 でも同じ)。 ログに at 行がないときは、同じ NPE の最初の発生をログの前のほうから探すか、起動オプションに -XX:-OmitStackTraceInFastThrow を付けて再現させます。
スタックトレースのどの行を見るか
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springNpe.ReportService': Instantiation of bean failed; nested exception is ...
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1158)
...(Spring の行が続く)
at SpringNpe.main(SpringNpe.java:43)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [SpringNpe$ReportService]: Constructor threw exception; nested exception is ...
...
Caused by: java.lang.NullPointerException: Cannot invoke "SpringNpe$UserRepository.findName(String)" because "this.userRepository" is null
at SpringNpe$ReportService.<init>(SpringNpe.java:24)
at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
...
| 状況 | 見る行 |
|---|---|
Caused by: がない |
1行目のメッセージと、最初に出てくる自分のパッケージの at 行 |
Caused by: がある |
いちばん下の Caused by: が根本の原因。その直後の自分のコードの at 行 |
at java.base/... や at org.springframework... が先頭に続く |
それらを読み飛ばし、自分のクラスが最初に出る行を探す |
<init> と出る |
コンストラクタの中で起きている |
lambda$main$0 と出る |
ラムダ式の中で起きている。行番号はラムダの中の行 |
... 13 more |
上の例外と同じ行が続くので省略された、という意味 |
スタックトレースの読み方は、関連記事「Javaスタックトレースの読み方|Caused byの意味と原因の探し方」で扱っています。
原因10パターンを再現コードと出力で確かめる
次の Npe.java を JDK 8 でも動くように書き、引数で原因を切り替えて実行しました。出力の行番号はこのファイルの行番号です。
import java.util.*;
import java.util.stream.*;
public class Npe {
// 1. 未初期化フィールド
static class Cart {
private List<String> items; // new していない
void add(String s) { items.add(s); }
}
// 5. メソッドの戻り値で使うクラス
static final class User {
private final String id, name;
User(String id, String name) { this.id = id; this.name = name; }
String id() { return id; }
String name() { return name; }
}
static User findUser(String id) {
return "u1".equals(id) ? new User("u1", null) : null; // 見つからないと null
}
// 6. DI されないフィールド(Spring の @Autowired が効かない状況と同じ形)
static class UserRepository { User findById(String id) { return findUser(id); } }
static class UserService {
UserRepository userRepository; // DI で入る想定。new すると null のまま
String nameOf(String id) { return userRepository.findById(id).name(); }
}
public static void main(String[] args) {
String c = args[0];
switch (c) {
case "field": new Cart().add("ペン"); break;
case "mapget": {
Map<String, Integer> stock = new HashMap<>();
stock.put("pen", 3);
int n = stock.get("apple"); // null を int に代入
break; }
case "array": {
String[] names = new String[3]; // 要素はすべて null
System.out.println(names[0].length());
break; }
case "arraynull": {
int[] scores = null;
System.out.println(scores.length);
break; }
case "unbox": {
Integer price = null;
int total = price + 100;
break; }
case "ternary": {
Integer discount = null;
boolean member = false;
Integer d = member ? 10 : discount; // 三項演算子の自動アンボクシング
System.out.println(d);
break; }
case "return": System.out.println(findUser("u9").name()); break;
case "chain": System.out.println(findUser("u1").name().length()); break;
case "autowired": System.out.println(new UserService().nameOf("u1")); break;
case "stream": {
List<User> users = Arrays.asList(new User("u1", "sato"), new User("u2", null));
System.out.println(users.stream().map(User::name).map(String::toUpperCase).collect(Collectors.toList()));
break; }
case "tomap": {
List<User> users = Arrays.asList(new User("u1", "sato"), new User("u2", null));
System.out.println(users.stream().collect(Collectors.toMap(User::id, User::name)));
break; }
case "optionalof": { String s = null; System.out.println(Optional.of(s)); break; }
case "equals": {
String status = null;
System.out.println("ACTIVE".equals(status));
System.out.println(Objects.equals(status, "ACTIVE"));
System.out.println(status.equals("ACTIVE"));
break; }
case "tostring": {
Object o = null;
System.out.println(String.valueOf(o));
System.out.println("値=" + o);
System.out.println(o.toString());
break; }
case "valueofchar": { System.out.println(String.valueOf((char[]) null)); break; }
case "switch": {
String s = null;
switch (s) { case "A": System.out.println("A"); break; default: System.out.println("other"); }
break; }
case "require": {
String email = null;
Objects.requireNonNull(email, "email は必須です");
break; }
default: System.out.println("?");
}
}
}
javac -g -d out Npe.java
java -cp out Npe field # 引数を mapget・array・unbox などに変えて実行
① 初期化していないフィールド
$ java -cp out Npe field # JDK 8
Exception in thread "main" java.lang.NullPointerException
at Npe$Cart.add(Npe.java:8)
at Npe.main(Npe.java:30)
$ java -cp out Npe field # JDK 25
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.add(Object)" because "this.items" is null
at Npe$Cart.add(Npe.java:8)
at Npe.main(Npe.java:30)
参照型のフィールドの初期値は null です。this. が付いていたらフィールドが原因です。
static class Cart {
private final List<String> items = new ArrayList<>(); // 宣言時に初期化。final で再代入も防ぐ
void add(String s) { items.add(s); }
}
② Map.get で存在しないキー(アンボクシング)
$ java -cp out Npe mapget # JDK 25
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
at Npe.main(Npe.java:34)
コードには intValue() を書いていないのに、メッセージに Integer.intValue() が出ます。 これは int n = map.get(...) の代入で、コンパイラが自動で intValue() を呼ぶコード(アンボクシング)を入れているためです。
int n = stock.getOrDefault("apple", 0); // 実行結果:apple=0
getOrDefault や merge の使い方は、関連記事「Java HashMapの使い方|ループ・getOrDefault・merge早見表」で扱っています。
③ 配列の要素が null・配列そのものが null
$ java -cp out Npe array # JDK 25
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "names[0]" is null
at Npe.main(Npe.java:38)
$ java -cp out Npe arraynull # JDK 25
Exception in thread "main" java.lang.NullPointerException: Cannot read the array length because "scores" is null
at Npe.main(Npe.java:42)
new String[3] は「null が3つ入った配列」です。names[0] のように [ ] 付きで出たら要素、Cannot read the array length なら配列そのものが null です。要素は代入してから使い、Arrays.fill(names, "") で空文字を入れておく方法もあります。
④ オートアンボクシング(Integer → int)と三項演算子
$ java -cp out Npe unbox # JDK 25
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "price" is null
at Npe.main(Npe.java:46)
$ java -cp out Npe ternary # JDK 25
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "discount" is null
at Npe.main(Npe.java:51)
三項演算子の例は、代入先が Integer なのに NPE になります。 member ? 10 : discount の片方が int(10)なので、式全体の型が int になり、discount がアンボクシングされるためです。両方を Integer にそろえる(member ? Integer.valueOf(10) : discount)と、null がそのまま代入されます。Integer と int の違いは、関連記事「Java Integerとintの違い|ラッパークラスとオートボクシング」で扱っています。
⑤ メソッドの戻り値が null
$ java -cp out Npe return # JDK 25
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Npe$User.name()" because the return value of "Npe.findUser(String)" is null
at Npe.main(Npe.java:54)
$ java -cp out Npe chain # JDK 25
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because the return value of "Npe$User.name()" is null
at Npe.main(Npe.java:55)
findUser("u1").name().length() のような連鎖でも、どのメソッドが null を返したか(User.name())まで特定できます。 JDK 8・11 では同じ行番号しか出ないため、findUser と name() のどちらかは分かりません。直し方は、戻り値を Optional にする方法です(後述の「予防」)。
⑥ Spring の @Autowired が効かない(null のまま)
Spring Framework 4.3.30 と JDK 25 で、次のコードを実行しました。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;
public class SpringNpe {
@Component
public static class UserRepository {
public String findName(String id) { return "sato"; }
}
@Component
public static class UserService {
@Autowired
private UserRepository userRepository;
public String nameOf(String id) { return userRepository.findName(id); }
}
@Component
public static class ReportService {
@Autowired
private UserRepository userRepository;
public ReportService() {
// コンストラクタの時点では、フィールドへの注入はまだ行われていない
System.out.println("init: " + userRepository.findName("u1"));
}
}
@Component
public static class FixedService {
private final UserRepository userRepository;
public FixedService(UserRepository userRepository) { // コンストラクタインジェクション
this.userRepository = userRepository;
System.out.println("init: " + userRepository.findName("u1"));
}
}
public static void main(String[] args) {
String c = args[0];
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(UserRepository.class, UserService.class);
if (c.equals("ctor")) ctx.register(ReportService.class);
if (c.equals("fixed")) ctx.register(FixedService.class);
ctx.refresh();
if (c.equals("bean")) System.out.println("getBean: " + ctx.getBean(UserService.class).nameOf("u1"));
if (c.equals("new")) System.out.println("new: " + new UserService().nameOf("u1"));
ctx.close();
}
}
| 引数 | 何をしたか | 結果 |
|---|---|---|
bean |
Spring から取り出した UserService を使う |
getBean: sato |
new |
自分で new UserService() した |
NullPointerException: Cannot invoke "SpringNpe$UserRepository.findName(String)" because "this.userRepository" is null |
ctor |
コンストラクタの中で @Autowired のフィールドを使う |
BeanCreationException。いちばん下の Caused by: が上と同じ NPE(at SpringNpe$ReportService.<init>) |
fixed |
コンストラクタインジェクションに変えた | init: sato |
@Autowired は、Spring が作ったオブジェクトにだけ値を入れます。 自分で new したオブジェクトには何も入らず、null のままです。また、フィールドへの注入はコンストラクタが終わった後に行われるので、コンストラクタの中ではまだ null です。
| 症状 | 原因 | 直し方 |
|---|---|---|
new ○○Service() した先で NPE |
Spring の管理外のオブジェクト | new せず、使う側にも注入する |
コンストラクタの中で NPE(<init> の行) |
フィールド注入はコンストラクタの後 | コンストラクタインジェクションにする |
static フィールドに @Autowired |
Spring は static フィールドに注入しない | static をやめる |
クラスに @Component などがない・スキャン対象のパッケージの外 |
Bean として登録されていない | アノテーションとパッケージ構成を確かめる(多くは起動時に別のエラーになる) |
Spring Framework 6 以降は、例外メッセージに nested exception is ... が付かない形に変わっていますが、Caused by: をたどる読み方は同じです。DI の書き方は、関連記事「Spring DIとは|@Autowiredとコンストラクタインジェクション」で扱っています。
⑦ JPA のエンティティと遅延ロード
JPA(Java のデータベース操作の標準仕様)では、次の3つの形で null が出てきます。ここは Hibernate ORM のドキュメントをもとに整理しています。
| 状況 | なぜ null か | 直し方 |
|---|---|---|
new Order() したエンティティの getItems().add(item) |
@OneToMany のコレクションを初期化していない(原因①と同じ) |
private List<Item> items = new ArrayList<>(); と宣言時に初期化 |
order.getCustomer().getName() |
外部キーが NULL の行では、任意(optional)の @ManyToOne は null |
関連が必須なら optional = false と NOT NULL 制約。任意なら null を前提に分岐 |
equals で other.name を直接読む |
遅延ロードのプロキシは、状態を読み込むまでフィールドに値を持たない | equals・hashCode ではゲッター(other.getName())を使い、型は instanceof で確かめる |
Hibernate のドキュメントでは、プロキシは「本物のエンティティのふりをするが、まだデータベースから状態を取ってきていないので状態を持たないオブジェクト」と説明されています。メソッドを呼ぶと、そこで状態を読み込みます。そのため equals() で相手のフィールドを直接読むと、プロキシの空のフィールド(null)を読むことになります。ドキュメントは、equals() では getClass() ではなく instanceof で型を確かめ、相手のフィールドはアクセサメソッド経由で読むよう注意しています。
@Override
public boolean equals(Object o) {
return o instanceof Book other // getClass() ではなく instanceof
&& getIsbn().equals(other.getIsbn()); // other.isbn ではなくゲッター
}
なお、セッション(永続化コンテキスト)が閉じた後にプロキシのメソッドを呼ぶと、NPE ではなく LazyInitializationException になります。必要な関連は JOIN FETCH などで先に読み込んでおく方法があり、関連記事「JPAのN+1問題の解決|JOIN FETCH等4手法の比較とコード全文」で扱っています。
⑧ Stream の途中に null がある
$ java -cp out Npe stream # JDK 25
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024)
...
$ java -cp out Npe tomap # JDK 25
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:220)
at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$0(Collectors.java:180)
...
Stream の NPE は、JDK 25 でもメッセージが出ないことがあります。 メソッド参照(String::toUpperCase)とラムダ式(n -> n.toUpperCase())を、別のファイル(StreamNpe.java)で比べました。
List<User> users = Arrays.asList(new User("u1", "sato"), new User("u2", null));
users.stream()
.map(User::name)
.map(String::toUpperCase) // メソッド参照
.collect(Collectors.toList()); // ← 17行目
users.stream()
.map(User::name)
.map(n -> n.toUpperCase()) // ラムダ式 ← 21行目
.collect(Collectors.toList());
| 書き方 | JDK 8 | JDK 25 |
|---|---|---|
| メソッド参照 | メッセージなし。自分のコードの行は StreamNpe.main(StreamNpe.java:17)(collect の行) |
同じ(メッセージなし、17行目) |
| ラムダ式 | メッセージなし。StreamNpe.lambda$main$0(StreamNpe.java:21) |
Cannot invoke "String.toUpperCase()" because "n" is null、lambda$main$0(StreamNpe.java:21) |
メソッド参照で NPE が出たら、一時的にラムダ式に書き換えると、どの要素の何が null かが分かります。 行番号が map ではなく collect の行を指すことにも注意します。
| Stream の操作 | null の扱い | 対処 |
|---|---|---|
map(String::toUpperCase) |
要素が null だと NPE | 前に filter(Objects::nonNull) |
Collectors.toMap |
値が null だと NPE(キーの重複がなくても) | null を除くか、collect(HashMap::new, (m, u) -> m.put(...), HashMap::putAll) |
Optional.of(x) |
x が null だと NPE | Optional.ofNullable(x) |
List.of(...)・Map.of(...) |
null を入れると NPE | null を入れない設計にする |
filter(Objects::nonNull) と、HashMap::new を使った collect に直した結果です(JDK 8・25 とも同じ)。
[SATO]
{u1=sato, u2=null}
toMap のキー重複のエラーは、関連記事「Java ListをMapに変換する方法 toMapのキー重複エラー対処」で扱っています。
⑨ equals の向き
$ java -cp out Npe equals # JDK 25
false
false
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "status" is null
at Npe.main(Npe.java:70)
| 書き方 | status が null のとき |
|---|---|
"ACTIVE".equals(status) |
false |
Objects.equals(status, "ACTIVE") |
false(両方 null なら true) |
status.equals("ACTIVE") |
NPE |
定数を左に書くか、Objects.equals を使います。 switch (status) も、status が null だと NPE になります(JDK 25 で Cannot invoke "String.hashCode()" because "<local5>" is null)。文字列の switch はコンパイラが作る一時変数を使うため、-g を付けても変数名ではなく <local5> と出ました。文字列比較の詳細は、関連記事「Javaの文字列比較|equalsと==の違いと正しい比べ方」で扱っています。
⑩ String.valueOf と toString
$ java -cp out Npe tostring # JDK 25
null
値=null
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Object.toString()" because "o" is null
at Npe.main(Npe.java:76)
$ java -cp out Npe valueofchar # JDK 25
Exception in thread "main" java.lang.NullPointerException: Cannot read the array length because "value" is null
at java.base/java.lang.String.<init>(String.java:286)
at java.base/java.lang.String.valueOf(String.java:4547)
at Npe.main(Npe.java:78)
| 書き方 | o が null のとき |
|---|---|
String.valueOf(o)(Object 型) |
文字列 "null" |
"値=" + o |
"値=null" |
o.toString() |
NPE |
String.valueOf((char[]) null) |
NPE(char[] を受け取るメソッドが選ばれる) |
String.valueOf は、char[] 型の null を渡すと NPE になります。 引数に直接 null と書いた場合も、コンパイラは valueOf(char[]) を選びます。メッセージの "value" は JDK の中の引数名で、スタックトレースの Npe.main(Npe.java:78) が自分のコードの行です。
NullPointerException の予防
予防策の早見表
| 方法 | いつ止まるか | 向いている場面 |
|---|---|---|
Objects.requireNonNull(x, "メッセージ") |
実行時、値を受け取った瞬間 | コンストラクタ・公開メソッドの引数チェック |
Optional を戻り値にする |
コンパイル時(扱いを書かないと値を取り出せない) | 「見つからないかもしれない」検索メソッド |
| record + コンパクトコンストラクタ | 実行時、オブジェクトを作る瞬間 | 値オブジェクト・DTO |
JSpecify の @NullMarked・@Nullable |
IDE・静的解析の時点 | チーム開発、ライブラリの公開 API |
| SpotBugs・IntelliJ IDEA の検査 | ビルド時・編集中 | CI での自動チェック |
Objects.requireNonNull で「渡された瞬間」に止める
record User(String id, String name) {
User {
Objects.requireNonNull(id, "id は必須です"); // 作る時点で止める
}
}
record: id は必須です
(new User(null, "x") の NPE を catch して getMessage() を表示した結果。JDK 21・25)
null を使う場所ではなく、null が入ってきた場所で止まるので、スタックトレースが原因の場所を直接指します。null のときに既定値を使う場合は、Java 9 以降の Objects.requireNonNullElse(x, 既定値) が使えます。null チェックの書き方は、関連記事「Java nullチェックの書き方|requireNonNullとisNull」で扱っています。
Optional で「無いかもしれない」を型にする
static Optional<User> findById(String id) {
return Optional.ofNullable(DB.get(id)); // 見つからないと Optional.empty()
}
findById("u1").map(User::name).orElse("(未登録)"); // sato
findById("u9").map(User::name).orElse("(未登録)"); // (未登録)
findById("u9").get(); // NoSuchElementException: No value present
Optional は戻り値に使います。 フィールドや引数には使わないのが一般的です。中身を確かめずに get() を呼ぶと、NPE の代わりに NoSuchElementException になります(JDK 25 で java.util.NoSuchElementException: No value present)。orElse と orElseGet の違いは、関連記事「Java Optionalの使い方|orElseとorElseGetの違いと使う場所」で扱っています。
JSpecify の @Nullable と SpotBugs で、動かす前に見つける
JSpecify は、null を許すかどうかを示すアノテーションの共通仕様です。@NullMarked を付けたクラスやパッケージでは「何も付いていない型は null を許さない」とし、null を許す所にだけ @Nullable を付けます。Spring Framework 7 は JSpecify のアノテーションを採用し、org.springframework.lang の @Nullable などを非推奨にしています(Spring Framework リファレンス)。
null の扱いを間違えたコードを書き、SpotBugs 4.10.5(静的解析ツール)で検査しました。
import java.util.Map;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class UserFinder {
private final Map<String, String> names;
public UserFinder(Map<String, String> names) { this.names = names; }
// 見つからないと null を返す
public @Nullable String find(String id) {
return names.get(id);
}
// 1. null を返し得るメソッドの戻り値を、確かめずに使う
public int nameLength(String id) {
return find(id).length();
}
// 2. null チェックの後で、null の経路でも使ってしまう
public String label(@Nullable String name) {
if (name == null) {
System.out.println("名前がありません");
}
return name.trim();
}
// 3. null を確実に参照する
public int alwaysNull() {
String s = null;
return s.length();
}
}
$ spotbugs -textui -effort:max -low -auxclasspath jspecify-1.0.1.jar c
H C NP: Possible null pointer dereference of name in UserFinder.label(String) Dereferenced at UserFinder.java:[line 25]
H C NP: Null pointer dereference of s in UserFinder.alwaysNull() Dereferenced at UserFinder.java:[line 31]
M D NP: Possible null pointer dereference in UserFinder.nameLength(String) due to return value of called method Dereferenced at UserFinder.java:[line 17]
L D NP: Load of known null value in UserFinder.alwaysNull() At UserFinder.java:[line 31]
(null 以外の指摘 EI_EXPOSE_REP2 が1件出ましたが、省略しています)
| 行 | SpotBugs のバグパターン | @Nullable を外した場合 |
|---|---|---|
| 17(戻り値を確かめずに使う) | NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE |
検出されなかった |
| 25(null の経路で使う) | NP_NULL_ON_SOME_PATH |
検出された |
| 31(必ず null) | NP_ALWAYS_NULL、NP_LOAD_OF_KNOWN_NULL_VALUE |
検出された |
find に @Nullable を付けたときだけ、17行目の「戻り値を確かめずに使う」が検出されました。 メソッドの戻り値が null になり得ることは、アノテーションで示さないとツールに伝わりません。SpotBugs は Maven・Gradle のプラグインからも実行でき、CI に組み込めます。
IntelliJ IDEA では、設定の Editor → Inspections → Probable bugs にある「Nullability and data flow problems」が、null になり得る変数の参照を警告します。IntelliJ IDEA のヘルプによると、既定で認識するアノテーションには JSpecify の @Nullable・@NonNull が含まれます。Spring のリファレンスでは、ビルド時に強制するツールとして NullAway も挙げられています。
コードレビューで見るところ
| 見るところ | 置き換え |
|---|---|
return null; しているメソッド |
Optional、空のコレクション(List.of())、空文字 |
Map.get の結果を int などに代入 |
getOrDefault |
Integer と int が混ざる三項演算子 |
両辺の型をそろえる |
変数.equals("定数") |
"定数".equals(変数)、Objects.equals |
自分で new している Spring の Bean |
注入する |
@Autowired のフィールドをコンストラクタで使う |
コンストラクタインジェクション |
Optional.get() を確かめずに呼ぶ |
orElse・orElseThrow |
Collectors.toMap の値が null になり得る |
null を除く、別の collect にする |
よくある質問
Q. java.lang.NullPointerException とは何ですか?
A. null の参照に対して、メソッド呼び出し・フィールドの読み書き・配列の操作などをしたときに JVM が投げる例外です。RuntimeException のサブクラス(非検査例外)なので、throws に書かなくてもコンパイルが通り、実行して初めて分かります。ほかの例外との違いは、関連記事「Java 例外一覧|種類・原因・対処の早見表と使い分け」で扱っています。
Q. NullPointerException のメッセージで、どこを見ればよいですか?
A. because "○○" is null の ○○ と、最初に出てくる自分のコードの at 行です。○○ が null だった式、at 行が起きた場所です。Caused by: がある場合は、いちばん下の Caused by: を見ます。
Q. メッセージに「Cannot invoke」が出ないのはなぜですか?
A. JDK 8・11 を使っているか、JDK の中のコード(Objects.requireNonNull など)が投げた NPE だからです。java -version で版を確かめます。同じ場所で何度も起きた NPE は、スタックトレースもメッセージも消えることがあり、-XX:-OmitStackTraceInFastThrow で確かめられます。
Q. null exception と NullPointerException は同じですか?
A. Java で null が原因の例外は NullPointerException です。C# では NullReferenceException、JavaScript では TypeError: Cannot read properties of null など、言語によって名前が違います。Kotlin では型で null を区別でき、関連記事「Kotlin null安全の使い方|?. ?: !! letの違い早見表」で扱っています。
Q. NullPointerException を catch すれば解決しますか?
A. catch するより、null が入ってくる原因を直すのが基本です。 catch で握りつぶすと、どこで null が生まれたかが分からなくなります。外部から来るデータは入口で検証し、内側のコードは null が来ない前提で書くと、!= null のチェックが減ります。
Q. 「ぬるぽ」とは何ですか? A. NullPointerException の日本での通称です。意味は同じです。
まとめ
- メッセージの
because "○○" is nullが、null だった式。this.○○はフィールド、the return value ofはメソッドの戻り値 - JDK 8・11 ではメッセージが出ない。JDK 15 以降は既定で出る(JDK 17・21・25 で同じ文面)
-gなしでコンパイルすると変数名は<local4>になる。JDK 内部が投げた NPE と、Stream のメソッド参照ではメッセージが出ない- 何度も起きた NPE は スタックトレースが消える。
-XX:-OmitStackTraceInFastThrowで再現できる - 原因は ①未初期化フィールド ②
Map.get③配列の要素 ④アンボクシング(三項演算子を含む) ⑤戻り値 ⑥@Autowiredにnew・コンストラクタ内での利用 ⑦JPA の関連とプロキシ ⑧Stream とtoMap⑨equalsの向き ⑩toStringとString.valueOf(char[]) - 予防は
Objects.requireNonNull・Optional・record・JSpecify の@Nullableと SpotBugs。@Nullableを付けると、戻り値の確かめ忘れを SpotBugs が検出した
参考・出典
確認日はいずれも 2026年10月16日です。
- JEP 358: Helpful NullPointerExceptions:https://openjdk.org/jeps/358
- Java SE 25 API NullPointerException:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/NullPointerException.html
- Java SE 25 API Objects:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Objects.html
- Java SE 25 API Optional:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Optional.html
- Spring Framework リファレンス「Null-safety」:https://docs.spring.io/spring-framework/reference/core/null-safety.html
- Hibernate ORM「An Introduction to Hibernate」(Proxies and lazy fetching/equals() and hashCode()):https://docs.hibernate.org/orm/7.0/introduction/html_single/
- JSpecify:https://jspecify.dev/
- SpotBugs バグの説明:https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html
- SpotBugs リリース:https://github.com/spotbugs/spotbugs/releases
- IntelliJ IDEA ヘルプ「Annotating source code」:https://www.jetbrains.com/help/idea/annotating-source-code.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「NullPointerExceptionの原因と対処法|原因別早見表【Java】」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。