StackTrace を一言でいうと
StackTrace(スタックトレース)は、
「例外が発生したときに、そのときの『呼び出し履歴』を上から順に全部並べたもの」
です。
どのクラスの、どのメソッドの、どの行で、
どういう順番で呼ばれて、最後どこで爆発したのか。
これが全部書いてあります。
StackTrace を読めるようになると、
「とりあえず動かない」状態から
「ここが原因だ」とピンポイントで突き止めるところまで、一気に近づきます。
まずは形に慣れる:典型的な StackTrace の例
代表的な例を眺めてみる
例えば、こんなコードを動かしたとします。
public class Main {
public static void main(String[] args) {
a();
}
static void a() {
b();
}
static void b() {
String s = null;
System.out.println(s.length()); // ここで NullPointerException
}
}
Java実行すると、コンソールにはこんな感じの StackTrace が出ます。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is null
at Main.b(Main.java:13)
at Main.a(Main.java:9)
at Main.main(Main.java:5)
Javaこの 3 行の「at ~」で、呼び出しの階段が逆順に並んでいます。
一番上(最初の at)が「例外が実際に発生した場所」。
その下に「そこを呼び出したメソッド」たちが続きます。
ざっくり読む順番
StackTrace を読むときの基本は、こうです。
いちばん上の「Exception ~」行
→ 例外の種類とメッセージ
最初の at ~ 行
→ 実際に爆発した箇所(真犯人)
その下の at ~ 行
→ 「爆発箇所までどうたどり着いたか」の道筋
まずこの 2 段階だけでいいので、順番に目で追う癖をつけてください。
一行を分解して読む:at Main.b(Main.java:13) の意味
at クラス名.メソッド名(ファイル名:行番号)
StackTrace の 1 行には、ほぼ必ずこの情報が入っています。
at Main.b(Main.java:13)
これは分解すると、
クラス名:Main
メソッド名:b
ソースファイル:Main.java
行番号:13
という意味です。
実際のコードを見返すと、
static void b() {
String s = null;
System.out.println(s.length()); // ← これが 13 行目だとする
}
Javaまさに「ここでやらかしている」と分かります。
IDE(IntelliJ や Eclipse)では、
StackTrace のこの部分をクリックすると、その行にジャンプできます。
迷わず「最初の at 行の file:line」を押さえる癖をつけると、デバッグが一気に楽になります。
上から下が「呼び出しの逆順」
先ほどの StackTrace はこうでした。
at Main.b(Main.java:13)
at Main.a(Main.java:9)
at Main.main(Main.java:5)
Javaこれは、
main が a を呼んだ
a が b を呼んだ
b の中で例外が起きた
という順番を、逆から並べている状態です。
イメージとしては、
一番上:最後に入った関数(現場)
一番下:最初に呼ばれた関数(スタート地点)
という構造です。
例外の「種類」と「メッセージ」をしっかり読む
例外名がまずヒントになる
StackTrace の一番上には、だいたいこういう行があります。
java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is null
Javaここには、
例外クラス名:java.lang.NullPointerException
メッセージ:Cannot invoke "String.length()" because "s" is null
が書かれています。
例外クラス名だけ見ても、かなり情報があります。
NullPointerException
→ null に対してメソッド呼び出しやフィールドアクセスをしている
ArrayIndexOutOfBoundsException
→ 配列の範囲外のインデックスにアクセスしている
IllegalArgumentException
→ メソッドの引数が不正(仕様違反)
NumberFormatException
→ 文字列を数値に変換しようとして失敗
例外名だけで「何系のミスか」が見えてきます。
メッセージは「もう一歩踏み込んだ説明」
Java 14 以降の NullPointerException などは、メッセージがだいぶ親切です。
Cannot invoke "String.length()" because "s" is null
「s が null だから length() を呼べない」と、
「どの変数が null だったか」まで教えてくれます。
ライブラリが投げてくる例外も、
たいていはメッセージに「具体的に何がダメだったか」を書いてくれます。
StackTrace を読むときは、
例外クラス名で “系統” をつかむ
メッセージで “具体的な事情” をつかむ
の二段構えで読み取ってください。
実戦パターンその1:ネストした呼び出しの中での NullPointerException
例のコード
public class A {
B b;
void doSomething() {
b.doWork();
}
}
public class B {
void doWork() {
System.out.println("working");
}
}
public class Main {
public static void main(String[] args) {
A a = new A(); // a.b は null のまま
a.doSomething(); // ここで NPE
}
}
Java実行すると、たとえばこんな StackTrace。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "B.doWork()" because "this.b" is null
at A.doSomething(A.java:4)
at Main.main(Main.java:7)
Java読み方
一番上の at 行:
at A.doSomething(A.java:4)
A クラスの doSomething メソッド、A.java の 4 行目で NullPointerException。
そこを開くと
b.doWork();
Javaとなっている。
メッセージは
because "this.b" is null
つまり、this.b が null。
ここまでくると、原因はかなりクリアですよね。
A のコンストラクタで b を初期化していない
あるいは setter などが呼ばれていない
という、オブジェクト生成の段階のバグです。
StackTrace を読むときにやっていることは、
爆発行を特定
メッセージから「どの変数が何状態」かを確認
そこに至るまでの初期化や代入の流れを疑う
という 3 ステップです。
実戦パターンその2:他クラスやライブラリ経由での例外
例のコード
ファイルから数値を読み取って合計するメソッドを考えます。
import java.io.IOException;
import java.nio.file.*;
import java.util.List;
public class FileSum {
public static int sum(String fileName) throws IOException {
Path path = Path.of(fileName);
List<String> lines = Files.readAllLines(path); // IOException の可能性
int sum = 0;
for (String line : lines) {
sum += Integer.parseInt(line); // NumberFormatException の可能性
}
return sum;
}
}
Javaこれを次のように呼び出すとします。
public class Main {
public static void main(String[] args) throws Exception {
int result = FileSum.sum("numbers.txt");
System.out.println("合計 = " + result);
}
}
Javanumbers.txt に数字以外の行が混じっていた場合の StackTrace を見てみましょう。
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:668)
at java.base/java.lang.Integer.parseInt(Integer.java:786)
at FileSum.sum(FileSum.java:12)
at Main.main(Main.java:4)
Java読み方のコツ
まず例外名とメッセージ。
NumberFormatException: For input string: "abc"
「”abc” を数値に変換しようとして失敗した」ことがわかる。
次に、自分のコードに関係する行を探します。
上から数行は Java ランタイム内部(java.lang.Integer など)なので、
直接は修正できません。
そこを過ぎると、
at FileSum.sum(FileSum.java:12)
が自分のコード。
FileSum.java の 12 行目を開くと、
sum += Integer.parseInt(line);
Java「数値にしたいはずの line に “abc” が入っていた」のが原因だと分かります。
あとは、lines の元ファイルの中身を確認する、
あるいは「空行やコメント行をスキップする」などの防御コードを書く、といった改善に進めます。
ライブラリ内部の行は「ヒント」程度に眺めて、
最終的には「自分のコード内で最も上にある at 行」に注目する、
という読み方を覚えておいてください。
よくある「StackTrace 読みの落とし穴」
先頭の 1 行だけ見て諦める
java.lang.NullPointerException という文字だけ見て、「あー NPE か…」で終わってしまうパターンは非常にもったいないです。
必ず、
例外名
メッセージ
最初の at 行(file:line)
の 3 つをセットで見てください。
そこまで見れば、「何となく雰囲気は分かる」ではなく、
「どの行で、何が、どんな状態だったか」まで手がかりが得られます。
一番下の行だけ見る
StackTrace の一番下(main メソッドなど)だけ見て、「この行が問題なんだ」と勘違いしてしまうこともあります。
一番下は多くの場合「スタート地点」であって、「爆発地点」ではありません。
「一番上の at 行」こそが真犯人候補
その下に続く行は“そこへ至る道”
という意識で、上から読む癖をつけてください。
まとめ:StackTrace を読む「筋肉」をつける
StackTrace の読み方を、初心者向けに整理するとこうなります。
一番上の「例外名+メッセージ」で、エラーの“種類”と“具体的な事情”を把握する
最初の at クラス.メソッド(ファイル:行) を見て、「爆発した行」に飛ぶ
その行の周辺コードを見ながら、「何が null?何が範囲外?どの入力が不正?」を考える
必要に応じて、その下の at 行(呼び出し元)をたどって、「どういう流れでそこに来たか」を追う
これが自然にできるようになると、
エラーに遭遇したときの「何から見ていいか分からない」状態が減っていきます。
