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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?