- JAVA
Java 例外一覧|種類・原因・対処の早見表と使い分け
Javaの例外は、Throwableを頂点に「検査例外(Exceptionの子孫)」「非検査例外(RuntimeExceptionの子孫)」「Error」の3種類に分かれ、どれに属するかで「try-catchかthrowsが必須かどうか」と「直し方の方向」が決まります。 検査例外はファイルやネットワークなどプログラムの外側の事情で起きるもの、非検査例外は主にコードの誤りで起きるもの、ErrorはJVM自体の深刻な状態を表します。
この記事では、よく出る例外39種を、再現できる最小コードと、実際に表示されたメッセージつきの一覧表にまとめました。さらに、自分で例外を投げるときに迷いやすいIllegalArgumentException・IllegalStateException・NullPointerException・UnsupportedOperationExceptionの使い分けを、判断フローとコードで示します。Throwableの階層図、Java 14で加わった詳細なNullPointerExceptionメッセージ、try-with-resourcesとマルチキャッチの書き方も載せています。
確認環境:OpenJDK 21.0.10(Linux x64)。記事中の出力はすべてこの環境で実際に表示されたものです。
Java例外の早見表(3種類の違い)
| 種類 | 継承元 | try-catchかthrows | 主な原因 | 基本の対処 |
|---|---|---|---|---|
| 検査例外 | Exception(RuntimeException以外) | 必須(無いとコンパイルエラー) | ファイルが無い、接続できないなど外側の事情 | catchして利用者に伝える、再試行する、上位へ投げる |
| 非検査例外 | RuntimeException | 任意 | nullの参照、範囲外の添字、引数の誤りなどコードの誤り | 原因のコードを直す。入力由来なら事前に確認する |
| Error | Error | 任意 | メモリやスタックの枯渇、クラスの初期化の問題 | 基本はcatchしない。設定やコードの構造を見直す |
Java 21のAPIドキュメント(Throwable)にも、「Throwableとそのサブクラスのうち、RuntimeExceptionとErrorのどちらのサブクラスでもないものが検査例外として扱われる」と書かれています。
出会うことの多い10個を先に見たい方向けの逆引きです。 メッセージは実際に表示されたものです。
| 表示されたメッセージ(先頭部分) | 例外 | まず確認すること |
|---|---|---|
| Cannot invoke "String.length()" because ... is null | NullPointerException | メッセージに出ている変数・メソッドの戻り値がnullになる経路 |
| Index 3 out of bounds for length 3 | ArrayIndexOutOfBoundsException | ループの終了条件(<= になっていないか) |
| For input string: "12a" | NumberFormatException | 変換前の文字列(空文字・全角・空白・記号) |
| class java.lang.String cannot be cast to class java.lang.Integer ... | ClassCastException | キャスト前に instanceof で型を確かめる |
| / by zero | ArithmeticException | 割る数が0になる入力 |
| (メッセージなし)ConcurrentModificationException | ConcurrentModificationException | 拡張forの中で remove していないか |
| (メッセージなし)UnsupportedOperationException | UnsupportedOperationException | List.of など変更できないリストに add していないか |
| No value present | NoSuchElementException | Optional が空のときの扱い |
| /no/such/file.txt (No such file or directory) | FileNotFoundException | パス、カレントディレクトリ、権限 |
| No suitable driver found for jdbc:... | SQLException | JDBCドライバがクラスパスにあるか、URLの書式 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java 例外一覧|種類・原因・対処の早見表と使い分け」
Throwableの階層図
graph TD
T[Throwable] --> E[Exception 検査例外]
T --> ER[Error]
E --> RT[RuntimeException 非検査例外]
E --> IO[IOException]
E --> SQL[SQLException]
E --> ROE[ReflectiveOperationException]
E --> IE[InterruptedException]
IO --> FNF[FileNotFoundException]
IO --> FSE[FileSystemException]
FSE --> NSF[NoSuchFileException]
ROE --> CNF[ClassNotFoundException]
RT --> NPE[NullPointerException]
RT --> IAE[IllegalArgumentException]
IAE --> NFE[NumberFormatException]
RT --> ISE[IllegalStateException]
RT --> IOOB[IndexOutOfBoundsException]
IOOB --> AIOOB[ArrayIndexOutOfBoundsException]
RT --> UOE[UnsupportedOperationException]
ER --> VME[VirtualMachineError]
VME --> SOE[StackOverflowError]
VME --> OOM[OutOfMemoryError]
ER --> LE[LinkageError]
LE --> NCDF[NoClassDefFoundError]
階層を知っておくと役に立つのは、catchの書き方を決めるときです。catch (IOException e) と書けば FileNotFoundException も NoSuchFileException もまとめて受け取れます。逆に catch (Exception e) は RuntimeException まで受け取るので、コードの誤りまで包み隠してしまうことがあります。
非検査例外の一覧(RuntimeExceptionの子孫)20種
「再現コード」は後述の確認用プログラムでそのまま動かしたものです。番号(U01など)は実行結果と対応しています。
| No | 例外 | 継承元 | 再現コード | メッセージ(実際の出力) | 対処 |
|---|---|---|---|---|---|
| U01 | NullPointerException | RuntimeException | String s = null; s.length(); |
Cannot invoke "String.length()" because ... is null | nullになる経路を直す。引数は Objects.requireNonNull で早めに確認 |
| U02 | ArrayIndexOutOfBoundsException | IndexOutOfBoundsException | int[] a = new int[3]; a[3] = 1; |
Index 3 out of bounds for length 3 | 添字は 0〜length-1。ループ条件を確認 |
| U03 | IndexOutOfBoundsException | RuntimeException | List.of(1, 2).get(2); |
Index: 2 Size: 2 | size() を確かめてから get |
| U04 | StringIndexOutOfBoundsException | IndexOutOfBoundsException | "abc".substring(5); |
Range [5, 3) out of bounds for length 3 | length() を確かめてから切り出す |
| U05 | ClassCastException | RuntimeException | Object o = "abc"; Integer i = (Integer) o; |
class java.lang.String cannot be cast to class java.lang.Integer ... | if (o instanceof Integer i) で確かめる |
| U06 | NumberFormatException | IllegalArgumentException | Integer.parseInt("12a"); |
For input string: "12a" | 入力を検証する。利用者の入力はcatchして案内する |
| U07 | IllegalArgumentException | RuntimeException | Thread.sleep(-1); |
timeout value is negative | 呼び出し側で引数の範囲を守る |
| U08 | IllegalStateException | RuntimeException | 同じStreamで count() を2回 |
stream has already been operated upon or closed | 呼ぶ順番・回数を見直す |
| U09 | UnsupportedOperationException | RuntimeException | List.of(1, 2).add(3); |
(メッセージなし) | new ArrayList<>(list) で変更できるコピーを作る |
| U10 | ArithmeticException | RuntimeException | int x = 1 / 0; |
/ by zero | 割る前に0を確かめる |
| U11 | ConcurrentModificationException | RuntimeException | 拡張forの中で l.remove(i) |
(メッセージなし) | removeIf か Iterator の remove() を使う |
| U12 | NoSuchElementException | RuntimeException | Optional.empty().get(); |
No value present | orElse orElseThrow ifPresent を使う |
| U13 | InputMismatchException | NoSuchElementException | new Scanner("abc").nextInt(); |
(メッセージなし) | hasNextInt() で確かめてから読む |
| U14 | DateTimeParseException | DateTimeException | LocalDate.parse("2026/10/03"); |
Text '2026/10/03' could not be parsed at index 4 | 書式に合う DateTimeFormatter を渡す |
| U15 | DateTimeException | RuntimeException | LocalDate.of(2026, 2, 30); |
Invalid date 'FEBRUARY 30' | 存在する日付かを確かめる |
| U16 | NegativeArraySizeException | RuntimeException | int[] a = new int[-1]; |
-1 | サイズの計算結果を確かめる |
| U17 | ArrayStoreException | RuntimeException | Object[] o = new String[1]; o[0] = 1; |
java.lang.Integer | 配列の実際の型に合う値を入れる |
| U18 | CompletionException | RuntimeException | CompletableFuture.supplyAsync(() -> 1 / 0).join(); |
java.lang.ArithmeticException: / by zero | getCause() で元の例外を見る |
| U19 | IllegalMonitorStateException | RuntimeException | new Object().notify(); |
current thread is not owner | synchronized の中で呼ぶ |
| U20 | IllegalFormatConversionException | IllegalFormatException | String.format("%d", "a"); |
d != java.lang.String | 書式指定子と引数の型をそろえる |
U03は List.of で作ったリストの出力です。ArrayList で同じことをすると「Index 2 out of bounds for length 2」と、文面が少し変わります(こちらも実行して確認しました)。同じ例外でも、投げているクラスによってメッセージの形が違うことがあります。
検査例外の一覧(try-catchかthrowsが必須)14種
| No | 例外 | 継承元 | 再現コード | メッセージ(実際の出力) | 対処 |
|---|---|---|---|---|---|
| C01 | IOException | Exception | 閉じた StringReader で read() |
Stream closed | 閉じた後に使わない。try-with-resourcesの範囲で使う |
| C02 | FileNotFoundException | IOException | new FileInputStream("/no/such/file.txt"); |
/no/such/file.txt (No such file or directory) | パスと権限を確認する |
| C03 | NoSuchFileException | FileSystemException | Files.readString(Path.of("/no/such/file.txt")); |
/no/such/file.txt | Files.exists で確かめるか、catchして案内する |
| C04 | EOFException | IOException | 1バイトしかないデータで readInt() |
(メッセージなし) | データの長さ・形式を確かめる |
| C05 | UnknownHostException | IOException | InetAddress.getByName("no-such-host.invalid"); |
no-such-host.invalid: Name or service not known | ホスト名とDNSの設定を確認する |
| C06 | ConnectException | SocketException | 待ち受けのないポートへ new Socket(...) |
Connection refused | 接続先が起動しているか、ポート番号を確認する |
| C07 | SQLException | Exception | DriverManager.getConnection("jdbc:nodb://x"); |
No suitable driver found for jdbc:nodb://x | ドライバのjarとURLの書式を確認する |
| C08 | ClassNotFoundException | ReflectiveOperationException | Class.forName("com.example.NoSuch"); |
com.example.NoSuch | クラス名とクラスパスを確認する |
| C09 | NoSuchMethodException | ReflectiveOperationException | String.class.getMethod("nope"); |
java.lang.String.nope() | メソッド名と引数の型を確認する |
| C10 | InterruptedException | Exception | 割り込み済みのスレッドで Thread.sleep(10) |
sleep interrupted | Thread.currentThread().interrupt() で割り込み状態を戻して処理を終える |
| C11 | ExecutionException | Exception | CompletableFuture.supplyAsync(() -> 1 / 0).get(); |
java.lang.ArithmeticException: / by zero | getCause() で元の例外を取り出す |
| C12 | TimeoutException | Exception | 終わらない Future に get(10, MILLISECONDS) |
(メッセージなし) | 待ち時間を見直す、再試行する |
| C13 | ParseException | Exception | new SimpleDateFormat("yyyy-MM-dd").parse("x"); |
Unparseable date: "x" | 書式を合わせる。新しいコードでは java.time を使う |
| C14 | URISyntaxException | Exception | new URI("http://a b"); |
Illegal character in authority at index 8: http://a b | 空白などをエンコードしてから渡す |
C05のメッセージの後半(Name or service not known)はOSが返す文面なので、Windowsなどでは異なります(今回確認したのはLinuxのみです)。
Errorの一覧 5種
| No | Error | 継承元 | 再現コード | メッセージ(実際の出力) | 対処 |
|---|---|---|---|---|---|
| E01 | StackOverflowError | VirtualMachineError | 終了条件のない再帰 | (メッセージなし) | 再帰の終了条件を確認する。深い再帰はループへ |
| E02 | OutOfMemoryError | VirtualMachineError | new long[Integer.MAX_VALUE]; |
Requested array size exceeds VM limit | 一度に確保する量を減らす。ヒープ不足なら -Xmx を見直す |
| E03 | ExceptionInInitializerError | LinkageError | static フィールドの初期化で例外 | (メッセージなし。Caused by に原因) | Caused by の例外を直す |
| E04 | NoClassDefFoundError | LinkageError | E03の後に同じクラスをもう一度使う | Could not initialize class ExceptionList$Config | 最初に出た E03 を探して直す |
| E05 | AssertionError | Error | throw new AssertionError("...") |
ここには来ない想定 | 前提が崩れた箇所を調べる |
E03とE04はセットで覚えると調べやすくなります。 staticフィールドの初期化で例外が起きると1回目は ExceptionInInitializerError、2回目以降は NoClassDefFoundError(Could not initialize class)になります。ログに後者だけが見えているときは、もっと前の時刻に前者が出ています。
一覧表の再現コードと実行結果(コピーして動かせます)
上の3つの表は、次のプログラムの出力を元にしています。ExceptionList.java として保存し、javac ExceptionList.java のあと java ExceptionList で実行できます。
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.sql.*;
import java.text.*;
import java.time.*;
import java.util.*;
import java.util.concurrent.*;
public class ExceptionList {
interface Code { void run() throws Throwable; }
static void show(String no, Code code) {
try {
code.run();
System.out.println(no + " 例外なし");
} catch (Throwable e) {
String kind = e instanceof Error ? "Error"
: e instanceof RuntimeException ? "非検査" : "検査";
System.out.println(no + " [" + kind + "] 親="
+ e.getClass().getSuperclass().getSimpleName() + " / " + e);
}
}
static int deep(int n) { return deep(n + 1); }
static class Config { static int size = Integer.parseInt("x"); }
public static void main(String[] args) {
// ---- 非検査例外(RuntimeException の子孫)
show("U01", () -> { String s = null; s.length(); });
show("U02", () -> { int[] a = new int[3]; a[3] = 1; });
show("U03", () -> { List.of(1, 2).get(2); });
show("U04", () -> { "abc".substring(5); });
show("U05", () -> { Object o = "abc"; Integer i = (Integer) o; });
show("U06", () -> { Integer.parseInt("12a"); });
show("U07", () -> { Thread.sleep(-1); });
show("U08", () -> { var st = List.of(1, 2).stream(); st.count(); st.count(); });
show("U09", () -> { List.of(1, 2).add(3); });
show("U10", () -> { int x = 1 / 0; });
show("U11", () -> { List<Integer> l = new ArrayList<>(List.of(1, 2, 3));
for (Integer i : l) if (i == 1) l.remove(i); });
show("U12", () -> { Optional.empty().get(); });
show("U13", () -> { new Scanner("abc").nextInt(); });
show("U14", () -> { LocalDate.parse("2026/10/03"); });
show("U15", () -> { LocalDate.of(2026, 2, 30); });
show("U16", () -> { int[] a = new int[-1]; });
show("U17", () -> { Object[] o = new String[1]; o[0] = 1; });
show("U18", () -> { CompletableFuture.supplyAsync(() -> 1 / 0).join(); });
show("U19", () -> { new Object().notify(); });
show("U20", () -> { String.format("%d", "a"); });
// ---- 検査例外(Exception の子孫で RuntimeException 以外)
show("C01", () -> { var r = new StringReader("a"); r.close(); r.read(); });
show("C02", () -> { new FileInputStream("/no/such/file.txt"); });
show("C03", () -> { Files.readString(Path.of("/no/such/file.txt")); });
show("C04", () -> { new DataInputStream(new ByteArrayInputStream(new byte[1])).readInt(); });
show("C05", () -> { InetAddress.getByName("no-such-host.invalid"); });
show("C06", () -> { new Socket("127.0.0.1", 8499); });
show("C07", () -> { DriverManager.getConnection("jdbc:nodb://x"); });
show("C08", () -> { Class.forName("com.example.NoSuch"); });
show("C09", () -> { String.class.getMethod("nope"); });
show("C10", () -> { Thread.currentThread().interrupt(); Thread.sleep(10); });
show("C11", () -> { CompletableFuture.supplyAsync(() -> 1 / 0).get(); });
show("C12", () -> { new CompletableFuture<Integer>().get(10, TimeUnit.MILLISECONDS); });
show("C13", () -> { new SimpleDateFormat("yyyy-MM-dd").parse("x"); });
show("C14", () -> { new URI("http://a b"); });
// ---- Error
show("E01", () -> { deep(0); });
show("E02", () -> { long[] big = new long[Integer.MAX_VALUE]; });
show("E03", () -> { int x = Config.size; });
show("E04", () -> { int x = Config.size; });
show("E05", () -> { throw new AssertionError("ここには来ない想定"); });
}
}
実行結果(OpenJDK 21.0.10。全39行のうち代表的な行を抜粋。残りの行のメッセージは上の表のとおりです):
U01 [非検査] 親=RuntimeException / java.lang.NullPointerException: Cannot invoke "String.length()" because "<local0>" is null
U03 [非検査] 親=RuntimeException / java.lang.IndexOutOfBoundsException: Index: 2 Size: 2
U05 [非検査] 親=RuntimeException / java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
U09 [非検査] 親=RuntimeException / java.lang.UnsupportedOperationException
C02 [検査] 親=IOException / java.io.FileNotFoundException: /no/such/file.txt (No such file or directory)
C11 [検査] 親=Exception / java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
E03 [Error] 親=LinkageError / java.lang.ExceptionInInitializerError
E04 [Error] 親=LinkageError / java.lang.NoClassDefFoundError: Could not initialize class ExceptionList$Config
「/」より右側が e.toString() の内容で、catchしなかった場合はこの前に Exception in thread "main" が付いたものがスタックトレースの1行目になります。U01の <local0> という表示については、次の次の章で説明します。
スタックトレースの読み方
例外をcatchしないと、次のようなスタックトレースが表示されます。
import java.util.*;
public class St {
static int total(List<String> prices) {
int sum = 0;
for (String p : prices) sum += toInt(p);
return sum;
}
static int toInt(String s) { return Integer.parseInt(s.trim()); }
public static void main(String[] args) {
System.out.println(total(List.of("100", "2OO", "300")));
}
}
Exception in thread "main" java.lang.NumberFormatException: For input string: "2OO"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:662)
at java.base/java.lang.Integer.parseInt(Integer.java:778)
at St.toInt(St.java:8)
at St.total(St.java:5)
at St.main(St.java:10)
| 見る場所 | 読み取れること |
|---|---|
| 1行目 | スレッド名、例外のクラス、メッセージ(ここでは数字のゼロではなく英字のOが混ざった "2OO") |
at の行(上から) |
例外が起きた場所から呼び出し元へさかのぼる順 |
java.base/ で始まる行 |
JDKの中のコード。読み飛ばしてよい |
| 自分のクラスが最初に出る行 | まず開く場所(ここでは St.java の8行目) |
Caused by: |
別の例外に包まれているときの元の例外。いちばん下の Caused by が根本の原因 |
「1行目でクラスとメッセージを読み、自分のクラスが出る最初のat行を開く」 が基本の手順です。
Java 14以降の詳細なNullPointerExceptionメッセージ
Java 14で、NullPointerExceptionのメッセージに「何がnullだったか」を表示する機能が加わりました(JEP 358)。JEPの文面では、Java 14の時点では既定で無効で、-XX:+ShowCodeDetailsInExceptionMessages を付けると有効になるとされています。今回のJava 21では、何も指定しなくても詳細なメッセージが表示されました。
import java.util.*;
public class Npe {
record Address(String city) {}
record User(String name, Address address) {}
static String title;
public static void main(String[] args) {
User user = new User("sato", null);
int[] counts = null;
Map<String, Integer> stock = new HashMap<>();
Runnable[] cases = {
() -> System.out.println(user.address().city().length()),
() -> System.out.println(title.trim()),
() -> System.out.println(counts[0]),
() -> { int n = stock.get("pen"); System.out.println(n); },
() -> { String s = args.length > 5 ? "x" : null; System.out.println(s.length()); },
};
for (int i = 0; i < cases.length; i++) {
try { cases[i].run(); }
catch (NullPointerException e) { System.out.println((i + 1) + ": " + e.getMessage()); }
}
String local = null;
local.isEmpty();
}
}
javac -g Npe.java でコンパイルして実行した結果:
1: Cannot invoke "Npe$Address.city()" because the return value of "Npe$User.address()" is null
2: Cannot invoke "String.trim()" because "Npe.title" is null
3: Cannot load from int array because "counts" is null
4: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
5: Cannot invoke "String.length()" because "s" is null
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.isEmpty()" because "local" is null
at Npe.main(Npe.java:23)
| 番号 | nullだったもの | メッセージの読み方 |
|---|---|---|
| 1 | メソッドの戻り値 | user.address() がnullを返したので city() を呼べない |
| 2 | staticフィールド | Npe.title がnull |
| 3 | 配列 | 配列 counts がnullなので要素を読めない |
| 4 | Mapに無いキー | get の戻り値がnullで、intへの自動変換(intValue)ができない |
| 5 | ローカル変数 | 変数 s がnull |
1のようにメソッドを続けて呼ぶ行でも、どの部分がnullだったかがわかるのがこの機能の利点です。4は「Mapに無いキーを get して int に入れた」ときの典型で、getOrDefault("pen", 0) にすると解消します。
ローカル変数名が local1 のように表示されるとき
同じコードを -g なしの javac Npe.java でコンパイルすると、3・5と最後の行は次のように変わりました。
3: Cannot load from int array because "<parameter1>" is null
5: Cannot invoke "String.length()" because "<local1>" is null
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.isEmpty()" because "<local5>" is null
ローカル変数や引数の名前は、クラスファイルにデバッグ情報(javac -g)が入っているときだけ表示されます。 入っていないと <local5> のような番号になります。フィールド名やメソッド名は -g がなくても表示されます(1・2・4は同じ出力でした)。MavenやGradleは、通常の設定ではデバッグ情報つきでコンパイルするのが一般的ですが、今回はビルドツール経由の確認は行っていません(未実行)。
-XX:-ShowCodeDetailsInExceptionMessages を付けて実行すると、メッセージは null(1行目は java.lang.NullPointerException だけ)に戻ることも確認しました。
IllegalArgument・IllegalState・NullPointer・UnsupportedOperationの使い分け
自分でメソッドを書くとき、「前提が満たされていない」ことを知らせる例外はこの4つが中心です。
判断フロー
flowchart TD
A[前提が満たされていない] --> B{その操作自体を<br>このオブジェクトは提供しているか}
B -- 提供していない --> UOE[UnsupportedOperationException]
B -- 提供している --> C{引数が原因か}
C -- はい --> D{引数がnullか}
D -- はい --> NPE[NullPointerException<br>Objects.requireNonNull]
D -- いいえ --> E{添字の範囲外か}
E -- はい --> IOOB[IndexOutOfBoundsException<br>Objects.checkIndex]
E -- いいえ --> IAE[IllegalArgumentException]
C -- いいえ 引数が正しくても呼べない --> ISE[IllegalStateException]
| 質問 | 答え | 投げる例外 | 例 |
|---|---|---|---|
| 引数を変えれば成功するか | する(値が範囲外・書式違い) | IllegalArgumentException | 優先度に9を渡した(1〜5のみ) |
| 引数がnullで、nullを受け付けない | ― | NullPointerException | 必須のタイトルがnull |
| 引数は正しいが、今の状態では呼べない | 順番や時期を変えれば成功する | IllegalStateException | 開始前に完了を呼んだ、閉じた後に読んだ |
| どんな引数・状態でも、その操作は提供していない | ― | UnsupportedOperationException | 読み取り専用のオブジェクトの変更メソッド |
見分け方のコツは「誰が何を直せば成功するか」 です。引数を直せば成功するなら IllegalArgumentException、呼ぶタイミングを直せば成功するなら IllegalStateException、どう呼んでも成功しないなら UnsupportedOperationException です。nullの引数に NullPointerException を使うのは、JDK標準の Objects.requireNonNull がそうしているためで、標準ライブラリと動きがそろいます。
具体例(実行結果つき)
import java.util.*;
public class Which {
static class Task {
private final String title;
private boolean started, done;
Task(String title) {
this.title = Objects.requireNonNull(title, "title は必須です");
if (title.isBlank()) throw new IllegalArgumentException("title は空にできません");
}
void setPriority(int p) {
if (p < 1 || p > 5)
throw new IllegalArgumentException("priority は 1〜5 で指定してください: " + p);
}
void start() { started = true; }
void finish() {
if (!started) throw new IllegalStateException("start() の前に finish() は呼べません");
done = true;
}
}
static class ReadOnlyTask extends Task {
ReadOnlyTask(String t) { super(t); }
@Override void setPriority(int p) {
throw new UnsupportedOperationException("読み取り専用のタスクは変更できません");
}
}
interface T { void run(); }
static void t(T b) { try { b.run(); } catch (RuntimeException e) { System.out.println(e); } }
public static void main(String[] a) {
t(() -> new Task(null));
t(() -> new Task(" "));
t(() -> new Task("資料作成").setPriority(9));
t(() -> new Task("資料作成").finish());
t(() -> new ReadOnlyTask("資料作成").setPriority(3));
t(() -> Objects.checkIndex(5, 3));
}
}
実行結果:
java.lang.NullPointerException: title は必須です
java.lang.IllegalArgumentException: title は空にできません
java.lang.IllegalArgumentException: priority は 1〜5 で指定してください: 9
java.lang.IllegalStateException: start() の前に finish() は呼べません
java.lang.UnsupportedOperationException: 読み取り専用のタスクは変更できません
java.lang.IndexOutOfBoundsException: Index 5 out of bounds for length 3
メッセージには「何が」「どういう条件で」「実際の値は何だったか」を入れると、ログを見た人がすぐ動けます(priority は 1〜5 で指定してください: 9 のように)。
try-with-resourcesとマルチキャッチの書き方
1つのコードで動きを確認する
import java.io.*;
import java.nio.file.*;
import java.time.*;
import java.time.format.*;
public class Trw {
static class Res implements AutoCloseable {
final String name;
Res(String name) { this.name = name; System.out.println("open " + name); }
void use(boolean boom) {
if (boom) throw new IllegalStateException(name + " の処理中に問題発生");
}
@Override public void close() throws IOException {
System.out.println("close " + name);
if (name.equals("B")) throw new IOException(name + " を閉じるときに問題発生");
}
}
static Object parse(String s) {
try {
if (s.contains("-")) return LocalDate.parse(s);
return Integer.parseInt(s);
} catch (NumberFormatException | DateTimeParseException e) { // マルチキャッチ
System.out.println("変換できない入力: " + s + " (" + e.getClass().getSimpleName() + ")");
return null;
}
}
public static void main(String[] args) {
try (Res a = new Res("A"); Res b = new Res("B")) { // try-with-resources
b.use(true);
} catch (IllegalStateException | IOException e) {
System.out.println("catch: " + e);
for (Throwable s : e.getSuppressed()) System.out.println(" suppressed: " + s);
} finally {
System.out.println("finally");
}
System.out.println(parse("123") + " / " + parse("2026-10-03") + " / "
+ parse("12a") + " / " + parse("2026-13-01"));
try {
try {
Files.readString(Path.of("/no/such/config.txt"));
} catch (IOException e) {
throw new UncheckedIOException("設定ファイルを読めません: config.txt", e); // 包み直す
}
} catch (RuntimeException e) {
System.out.println(e);
System.out.println("Caused by: " + e.getCause());
}
}
}
実行結果:
open A
open B
close B
close A
catch: java.lang.IllegalStateException: B の処理中に問題発生
suppressed: java.io.IOException: B を閉じるときに問題発生
finally
変換できない入力: 12a (NumberFormatException)
変換できない入力: 2026-13-01 (DateTimeParseException)
123 / 2026-10-03 / null / null
java.io.UncheckedIOException: 設定ファイルを読めません: config.txt
Caused by: java.nio.file.NoSuchFileException: /no/such/config.txt
- try-with-resources は、開いた順と逆の順(B → A)で自動的に閉じます。 閉じる処理はcatchやfinallyより先に実行されます。
- 本体の例外と close の例外が両方起きたときは、本体の例外が投げられ、close の例外は
getSuppressed()で取り出せます。 元の原因が残るので調べやすくなります。 - マルチキャッチは
|で区切って、同じ処理をする例外をまとめます。 変数eは暗黙的にfinalで、再代入はできません。 - 検査例外を非検査例外に包み直すときは、第2引数に元の例外(cause)を渡します。 渡し忘れると
Caused by:が出ず、根本の原因が追えなくなります。
マルチキャッチと検査例外のコンパイルエラー
次の3つは、実際に javac 21 で表示されたエラーです。
| 書いたコード | javacのエラー文 | 直し方 |
|---|---|---|
catch (FileNotFoundException | IOException e) |
error: Alternatives in a multi-catch statement cannot be related by subclassing | 親子関係にある例外は親(IOException)だけを書く |
try の中で投げられない検査例外を catch (IOException e) |
error: exception IOException is never thrown in body of corresponding try statement | その catch を外す |
new FileInputStream("x"); をtryもthrowsもなしで書く |
error: unreported exception FileNotFoundException; must be caught or declared to be thrown | try-catchで囲むか、メソッドに throws を付ける |
3つめが「検査例外はtry-catchかthrowsが必須」の正体です。非検査例外にはこのチェックがないので、何も書かなくてもコンパイルは通ります。
finallyとスレッドでの注意点(実行して確認)
| 書き方 | 実際の動き | こうするとよい |
|---|---|---|
finally の中で return 1; |
tryで投げた例外が消えて、1 が返る | finally で return しない |
| finally の中で別の例外を投げる | 元の例外が消える(getSuppressed() も0件だった) |
後始末は try-with-resources に任せる |
| 別スレッドの中で例外 | Exception in thread "Thread-0" ... と表示され、mainは続行する |
ExecutorService と Future で結果と例外を受け取る |
catch (Exception e) {} と空にする |
何も表示されず、原因が追えなくなる | 最低でもログに出す。対処できないなら捕まえずに上へ任せる |
例外への対処を決める判断表
| 例外の種類 | 捕まえる場所 | 対処の考え方 |
|---|---|---|
| 入力由来の非検査例外(NumberFormatException、DateTimeParseException) | 入力を受け取った近く | catchして、利用者にわかる言葉で入力し直しを案内する |
| コードの誤りによる非検査例外(NullPointerException、IndexOutOfBounds、ClassCast) | 個別には捕まえない | スタックトレースの場所を直す。全体の入口で1か所だけログに記録する |
| 外側の事情による検査例外(IOException、SQLException) | 対処できる層 | 再試行・代わりの手段・利用者への案内。対処できない層では throws で上へ |
| 待ち合わせ系(InterruptedException) | その場 | 割り込み状態を戻して、処理を早めに終える |
| 包まれた例外(ExecutionException、CompletionException) | 結果を受け取る場所 | getCause() を取り出して中身で判断する |
| Error(OutOfMemoryError、StackOverflowError) | 基本は捕まえない | 設定(-Xmx、-Xss)やデータ量、再帰の構造を見直す |
よくある質問
Q. 検査例外と非検査例外は、どう見分けますか? A. 継承元で決まります。RuntimeExceptionの子孫とErrorの子孫が非検査、それ以外のThrowableの子孫が検査例外です。迷ったら、try-catchもthrowsも書かずにコンパイルしてみると、検査例外なら「unreported exception ...; must be caught or declared to be thrown」と表示されます。
Q. IllegalArgumentExceptionとIllegalStateExceptionで迷ったら?
A. 「引数を変えれば成功するか」で考えます。成功するなら IllegalArgumentException、引数が正しくても今の状態では呼べない(順番や時期の問題)なら IllegalStateException です。引数がnullのときは Objects.requireNonNull を使い、NullPointerException にするとJDK標準とそろいます。
Q. catch (Exception e) でまとめて捕まえてもよいですか? A. 処理の入口(リクエストの受け口やバッチの最上位)で1か所だけ、ログに記録する目的で使うのは一般的です。個々の処理の中で使うと、NullPointerExceptionのようなコードの誤りまで受け取ってしまい、原因に気づきにくくなります。対処できる例外だけを、種類を指定して捕まえるのがおすすめです。
Q. Errorはcatchしてもよいですか? A. 文法上は書けますが、通常は捕まえません。OutOfMemoryErrorなどはJVMの状態そのものが不安定になっている合図で、処理を続けても別の場所で問題が出やすいためです。原因(データ量、再帰の深さ、static初期化の例外)を調べて直します。
まとめ
- 例外は「検査例外」「非検査例外」「Error」の3種類。 RuntimeExceptionとErrorの子孫以外が検査例外で、try-catchかthrowsが必須です。
- 非検査例外の多くはコードの誤りの合図です。捕まえて隠すより、スタックトレースで自分のクラスが最初に出る行を開いて直します。
- Java 21ではNullPointerExceptionのメッセージに「何がnullだったか」が表示されます。 ローカル変数名まで出すには
javac -gのデバッグ情報が必要です。 - 引数を直せば成功するなら IllegalArgumentException、タイミングを直せば成功するなら IllegalStateException、どう呼んでも提供していないなら UnsupportedOperationException、nullの引数は NullPointerException を投げます。
- try-with-resources は逆順に自動で閉じ、close の例外は suppressed に残ります。 マルチキャッチは親子関係のない例外を
|でまとめます。 - 包み直すときは cause を必ず渡すと、
Caused by:から根本の原因をたどれます。 - ExceptionInInitializerError と NoClassDefFoundError(Could not initialize class)はセットで、先に出た前者の Caused by を直します。
参考・出典
- Java SE 21 API ドキュメント「Throwable」(検査例外の定義を確認) https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Throwable.html
- JEP 358: Helpful NullPointerExceptions(Java 14で提供、フラグ名、ローカル変数名には
javac -gが必要という記述を確認) https://openjdk.org/jeps/358
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java 例外一覧|種類・原因・対処の早見表と使い分け」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。