例外処理とは、プログラムの実行中に発生するエラーや予期しない事態に対処するための仕組みのこと。例外処理を適切にプログラムに組み込むことで、プログラムがエラーで突然止まってしまうのを防いだり、エラーが発生しても適切にプログラムの実行を続けることができたりするようになります。

例えばほとんどのプログラミング言語に共通する事象として「0で割り算をしようとするとエラーが発生する」というものがあります。これを例外処理なしで実行すると、プログラムはそこで止まってしまいます。
が、しかし、例外処理を使えばこのエラーを検知して「エラーが発生しました」というメッセージを表示し、その後のプログラムを続行することができるようになります。
0で割り算をする、という以外にも自分で新たな例外を作成することで、例えば「年齢は0以上でなければならない」というルールを守らない入力があった場合に、専用のエラーメッセージを表示することができるように。
このページでは、この例外処理をJavaで実現する方法(try-catch構文)の使い方やコツ・注意点を初心者向けにわかりやすく解説します。
Java:try-catchの基本構文
Javaではプログラム中にエラーが発生する可能性のある部分をtryブロックで囲み、エラーが発生した場合の処理をcatchブロックで定義することで例外処理を利用することができるようになります。
以下は、try-catchの基本的な使い方を示す例。

この例では、0で割り算を行い、発生したエラーをキャッチして処理しています。
public class TryCatchExample { public static void main(String[] args) { try { // 例外が発生する可能性のあるコード int result = 10 / 0; } catch (ArithmeticException e) { // 例外が発生した場合の処理 System.out.println("エラーが発生しました: " + e.getMessage()); } } } // 出力結果: // エラーが発生しました: / by zero
この例では10を0で割ろうとしていますが、これは「ArithmeticException」というエラーを引き起こします。tryブロック内でエラーが発生すると、プログラムはcatchブロックに移り「ArithmeticException」が発生した場合にエラーメッセージを表示する仕組み。
このように、try-catchを使うことで、エラーが発生してもプログラムを停止させず、適切なエラーメッセージを表示して処理を続行することができます。
ポイント try-catchの構文ルール
try { // 例外が発生する可能性のあるコード } catch (例外の種類 例外の変数) { // 例外が発生した場合の処理 }
- tryブロック: 例外が発生する可能性のあるコードを囲む。
- catchブロック: 例外が発生した場合に実行されるコードを定義。
複数のcatchブロック
Javaでは、複数の異なる種類の例外をキャッチするために複数のcatchブロックを使用することができるため、異なる種類のエラーに対して異なる処理を行うことも可能です。
↓の例では、配列の範囲外アクセスを試みています。最初のcatchブロックが「ArrayIndexOutOfBoundsException」をキャッチし「配列のインデックスが範囲外です」というメッセージを表示します。もし他の種類の例外が発生した場合は、次のcatchブロックでキャッチされ、「その他のエラー」というメッセージを表示します。
public class MultipleCatchExample { public static void main(String[] args) { try { int[] numbers = {1, 2, 3}; System.out.println(numbers[5]); // 配列の範囲外アクセス } catch (ArrayIndexOutOfBoundsException e) { // 配列の範囲外アクセスが発生した場合の処理 System.out.println("配列のインデックスが範囲外です: " + e.getMessage()); } catch (Exception e) { // その他の例外が発生した場合の処理 System.out.println("その他のエラー: " + e.getMessage()); } } } // 出力結果: // 配列のインデックスが範囲外です: Index 5 out of bounds for length 3
このように、複数のcatchブロックを使うことで、異なる種類の例外に対して適切なエラーハンドリングを行うことができます。
ポイント 複数のcatchブロックを利用するパターンの構文ルール
try { // 例外が発生する可能性のあるコード } catch (具体的な例外1 例外の変数) { // 具体的な例外1が発生した場合の処理 } catch (具体的な例外2 例外の変数) { // 具体的な例外2が発生した場合の処理 } catch (一般的な例外 例外の変数) { // その他の例外が発生した場合の処理 }
- 複数のcatchブロック: tryブロック内で発生する可能性のある異なる種類の例外ごとにcatchブロックを定義する。
- 例外の順序: より具体的な例外から順に記述し、最後に一般的な例外をキャッチするcatchブロックを記述する。
try-catch-finally
Javaの例外処理では、finallyブロックを使用して、例外の発生有無にかかわらず必ず実行されるコードを定義することができます。リソースの解放や後処理など、どんな状況でも実行しておきたい処理を記述するために用いられます。

以下は、finallyブロックの基本的な使い方を示す例。この例では、0で割り算を行い、発生したエラーをキャッチした後、必ず実行される処理をfinallyブロックで定義しています。
public class FinallyExample { public static void main(String[] args) { try { // 例外が発生する可能性のあるコード int result = 10 / 0; } catch (ArithmeticException e) { // 例外が発生した場合の処理 System.out.println("エラーが発生しました: " + e.getMessage()); } finally { // 必ず実行される処理 System.out.println("このコードは必ず実行されます。"); } } } // 出力結果: // エラーが発生しました: / by zero // このコードは必ず実行されます。
ポイント finallyブロックの使い方
try { // 例外が発生する可能性のあるコード } catch (例外の種類 例外の変数) { // 例外が発生した場合の処理 } finally { // 必ず実行される処理 }
- tryブロック: エラーが発生する可能性のあるコードを囲む。
- catchブロック: エラーが発生した場合に実行されるコードを定義する。
- finallyブロック: 例外の有無にかかわらず必ず実行されるコードを定義する。
ポイント 上記は①②③の順に記述しないといけません。仮に以下のように順番を変更するとコンパイルエラーが発生します。
//以下は順番が不正なのでコンパイルエラーが発生する try { // 例外が発生する可能性のあるコード } finally { // 必ず実行される処理 } catch (例外の種類 例外の変数) { // 例外が発生した場合の処理 }
ポイント try-catch-finally
構文では、複数記述できるのはcatch
のみです(tryとfinallyは1つだけ)。→異なる種類の例外(例:IOException
やNullPointerException
)を個別に処理できるようにするためです。
public class TryCatchExample { public static void main(String[] args) { try { int result = 10 / 0; // 例外発生(ArithmeticException) } catch (ArithmeticException e) { System.out.println("算術エラーが発生しました: " + e.getMessage()); } catch (NullPointerException e) { System.out.println("ヌルポ発生!"); } finally { System.out.println("処理終了。"); // finallyは1つしか定義できない } } }
- Qcatch は結局何を受け取っているのか?
- A
catch ブロックは「投げられた例外オブジェクト」を受け取っている、と捉えると分かりやすいです。
少し比喩すると、
- メソッドが処理中に「ヤバい!」と思ったら「エラーが起きたぞ!」という手紙を投げる(
throw
)。 - 上位のどこかのメソッドが「その手紙は自分が受け取るよ」と言って、それを受け取り(
catch
)、中に書かれたエラー情報を読み取って何とか対処する。
こう考えると、「catch ブロックはいったい何をキャッチしているのか?」は、「エラーの詳細が詰まった“特別なオブジェクト”を受け取っている」ということになります。
- メソッドが処理中に「ヤバい!」と思ったら「エラーが起きたぞ!」という手紙を投げる(
カスタム例外
Javaでは標準で提供される例外クラス(例:NullPointerException
やArrayIndexOutOfBoundsException
)を使ってエラーを処理することができますが、同時に特定のアプリケーションに合わせて独自の例外を作成することも可能です。これを「カスタム例外」と呼びます。カスタム例外を使うことで、特定のエラー状況に対してよりわかりやすく、意味のあるエラーメッセージを提供することができます。
カスタム例外は、Javaの標準例外クラスを継承して作成する独自の例外クラスです。これにより、特定のビジネスロジックやアプリケーションの要件に応じたエラー処理を行うことができます。例えば、年齢が負の値になってはいけないというルールを守らない入力があった場合に、専用のエラーメッセージを提供するためにカスタム例外を作成します。
参考 Java「クラス」とは? / Java「クラスの継承」とは?
カスタム例外の作成
- カスタム例外クラスの定義
- 標準の例外クラス(例:
Exception
やRuntimeException
)を継承して新しいクラスを作成する
- 標準の例外クラス(例:
- カスタム例外の使用
- 必要に応じて、カスタム例外をスロー(throw)し、catchブロックで処理。

早速、カスタム例外を作成してみます。
まず、カスタム例外クラスを定義します。このクラスはException
クラスを継承します。
// カスタム例外クラスの定義 public class CustomException extends Exception { public CustomException(String message) { super(message); } }
次に、カスタム例外をスローし、catchブロックで処理します。この例では、入力された値が0未満の場合にカスタム例外をスローします。
public class CustomExceptionExample { public static void main(String[] args) { try { checkValue(-1); } catch (CustomException e) { // カスタム例外が発生した場合の処理 System.out.println("カスタム例外が発生しました: " + e.getMessage()); } } // 値をチェックし、条件に合わない場合にカスタム例外をスローするメソッド public static void checkValue(int value) throws CustomException { if (value < 0) { throw new CustomException("値は0以上でなければなりません。"); } } } // 出力結果: // カスタム例外が発生しました: 値は0以上でなければなりません。
checkValue
メソッドが負の値を受け取ると、CustomException
をスローするというコード。main
メソッド内でこのメソッドを呼び出し、カスタム例外がスローされた場合はcatchブロックで処理します。このように、カスタム例外を使用することで、特定のエラー状況に対して意味のあるエラーメッセージを提供し、プログラムの可読性とメンテナンス性を向上させることができます。
ポイント throwの構文ルール
throw new 例外クラス名("エラーメッセージ");
- throw キーワード: 例外オブジェクトをスローするために使用します。
- 例外オブジェクト:
new
キーワードを使用して例外クラスのインスタンスを作成します。

以下は、throw
キーワードを使用して標準のIllegalArgumentException
をスローする例。
public class ThrowExample { public static void main(String[] args) { checkValue(-1); } public static void checkValue(int value) { if (value < 0) { throw new IllegalArgumentException("値は0以上でなければなりません。"); } System.out.println("値は正しいです: " + value); } } // 出力結果: // Exception in thread "main" java.lang.IllegalArgumentException: 値は0以上でなければなりません。 // at ThrowExample.checkValue(ThrowExample.java:8) // at ThrowExample.main(ThrowExample.java:4)
throw の構文の意味
throw new NullPointerException("オブジェクトがnullです");
↑のコードの意味をより詳しく分解すると
- new を使って「オブジェクトがnullです」というメッセージを持つ
NullPointerException
の新しいインスタンス(例外オブジェクト)を作ります。 - throw によって、その例外オブジェクトをすぐに投げ(発生させ)、プログラムの実行が中断され、適切な
catch
ブロックで処理されるか、なければエラーとしてプログラムを終了させる。
という意味になります。ここで、①のnew は例外クラスをインスタンス化しているだけなので、実は以下のように記述しても意味は全く同じです。
NullPointerException a = new NullPointerException("オブジェクトがnullです"); throw a;
例外処理をより詳しく、実践的に理解する

ここからはこれまでに解説した基本を踏まえ、より例外処理を実践的に使いこなせるようにその動作原理やJavaの裏側についてより詳細に解説していきます。
Javaにおける例外処理は、プログラムの実行中に何らかの異常事態(エラー状況)が発生した場合に、その処理を呼び出し元など上位のコードに通知し、適切に対処できるようにする仕組みでした。
例外を使うことで、エラー発生時に戻り値などで状態を示す代わりに、「本来の処理フローを中断し、対処が可能な場所までジャンプする」形で流れを切り替えることができます。
整理:Javaの例外処理の基本的な流れ
- 例外が発生(throw)する
throw new SomeException("エラー理由");
のように明示的に投げられるケース。- 読み込み処理中にファイルが見つからない、配列の範囲外にアクセスした、などシステム側が検知して自動的に発生させるケース。
- スタックの巻き戻し(stack unwinding)が行われる
- 例外が発生すると、Java仮想マシン(JVM)は呼び出しスタックを下にたどっていき、対応する
catch
節を探す。 - 見つかるまでメソッド呼び出しをどんどん終了(巻き戻し)していく。
- 例外が発生すると、Java仮想マシン(JVM)は呼び出しスタックを下にたどっていき、対応する
- 対応する
catch
節で処理される- スタック上で「指定された型またはそのサブクラスの例外を捕捉できる」
catch
が存在する場合、その箇所に処理が移る。 - 例:
catch (IOException e) { ... }
であれば、IOException
とそのサブクラスを捕捉する。
- スタック上で「指定された型またはそのサブクラスの例外を捕捉できる」
- 例外が捕捉できない場合
- どのメソッドを遡っても該当の
catch
節がないなら、最終的に JVM が受け取ってエラーとしてプログラムが終了する。
- どのメソッドを遡っても該当の
- 任意で
finally
節が実行される- 例外の有無にかかわらず、リソース解放や終了処理は
finally
節に書いておくことで必ず実行できる。
- 例外の有無にかかわらず、リソース解放や終了処理は
スタックの巻き戻し
たとえば以下のようにメソッドが呼び出されていたとします。
main() → methodA() → methodB() → methodC()
- methodC() 内で例外が発生
throw new SomeException("何らかのエラー");
によって例外オブジェクトが作られ投げられる。 - methodC() 内に対応する
catch
がなければメソッドを終了(スタックフレーム破棄)methodC()
のローカル変数や状態は失われ、呼び出し元のmethodB()
に戻る。
- methodB() で対応する
catch
を探す- なければ
methodB()
も終了(破棄)し、さらに呼び出し元のmethodA()
に戻る。
- なければ
- methodA() でも見つからなければ終了
- 同様に破棄して、最終的に
main()
メソッドに行き着く。
- 同様に破棄して、最終的に
- すべての呼び出し元に
catch
がなければ、JVM が例外を受け取ってアプリケーションを終了する- スタックトレース(どのメソッドで例外が発生したか)が標準エラーに出力される。
このように、スタック上の各メソッドフレームを「巻き戻し」しながら、例外を補足できるブロックを探していく仕組みがスタックの巻き戻しです。
例外クラス
Throwable を頂点とした階層構造
Java の例外は、クラス階層として見ると以下のような構造になっています。
java.lang.Object └─ java.lang.Throwable ├─ java.lang.Error └─ java.lang.Exception └─ java.lang.RuntimeException └─ ...
Throwable
- Java で「投げられる(throw できる)もの」を表す最上位のクラスです。
Error
とException
の2系統に大きく分かれます。
Error
- JVM の致命的なエラー(例:
OutOfMemoryError
やStackOverflowError
)が含まれます。 - 通常のアプリケーションでは捕捉しても対処が難しいため、ほとんどの場合は積極的に扱いません。
Exception
- アプリケーションで処理可能な問題が含まれます。
IOException
、SQLException
、ClassNotFoundException
などが代表例です。
RuntimeException
Exception
のサブクラスですが、RuntimeException
やそのサブクラスは「実行時例外」として扱われます(例:NullPointerException
、ArrayIndexOutOfBoundsException
、IllegalArgumentException
など)。- チェック例外(後述)と異なり、コンパイラが
try-catch
やthrows
宣言を強要しない性質を持ちます。
チェック例外と実行時例外(unchecked例外)の違い
Javaでは、プログラム中で起こる「問題」をオブジェクトとして扱います。その元となるのが Throwable クラスです。ここから「例外」と「エラー」という大きく2つのグループに分かれます。
ポイント Throwable:全ての問題の元
- Throwable
Javaのすべての「問題」はこのクラスか、そのサブクラスのインスタンスとして表されます。つまり、何か問題が起こると、Throwable(またはその派生クラス)のオブジェクトが生成されます。
ポイント Error:システムの大問題
- Error
- システムそのものや環境に起因する重大な問題を表します。
- 例としては、メモリ不足(
OutOfMemoryError
)やスタックオーバーフロー(StackOverflowError
)があります。 - 通常、Errorはプログラム側で捕まえて対処するものではなく、発生した場合はプログラムの実行を続けるのが難しいため、あまり触るべきではありません。
ポイント Exception:プログラムが対処できる問題
- Exception
プログラムの動作中に発生する「例外的な状況」を表し、これらは開発者が捕まえて適切に対処できるものです。Exceptionはさらに次の2種類に分けられます。- チェック例外(Checked Exception)
- コンパイル時に「この例外が発生するかもしれないから、対処しなさい」と指摘される例外です。
- 例:ファイル操作で使われる
IOException
など - メソッド内で発生が予測される場合は、
try-catch
で捕まえるか、メソッド宣言にthrows
を付ける必要があります。
- 非チェック例外(Unchecked Exception)
- プログラムのバグや論理的なミスにより発生する例外で、コンパイル時に対処を強制されません。
- 例:
NullPointerException
、ArithmeticException
など - 主に
RuntimeException
のサブクラスとして実装されています。
- チェック例外(Checked Exception)
ポイント ユーザが作る独自の例外クラス
自分のプログラムで、特定の状況に合わせた例外を作りたい場合、以下のどちらかを継承して作成します。
- チェック例外として作成する場合
Exception
を継承します。
public class MyCheckedException extends Exception { public MyCheckedException(String message) { super(message); } }
この場合、例外を発生させるメソッドでは、呼び出し元に対して例外処理を強制されます。
- 非チェック例外として作成する場合
RuntimeException
を継承します。
public class MyUncheckedException extends RuntimeException { public MyUncheckedException(String message) { super(message); } } // この場合、呼び出し元は例外処理を必ずしも記述する必要はありません。
例外クラスの役割
- 情報を持つことができる
- 例外クラスはオブジェクトなので、コンストラクタでメッセージやエラーコードなどの詳細情報を持たせることが可能です。
- 例外を捕捉した側で
e.getMessage()
のようにしてメッセージを参照し、ログ出力やユーザへの通知に活用できます。
- 継承で種類を表現できる
- 独自の例外クラスを定義することで「自分のアプリケーションで起こりうる特有のエラー」を表現できます。
- サブクラスを作ることでエラーの粒度を細分化し、よりきめ細かくハンドリング可能にします。
- ポリモーフィズムの適用
- スーパークラス型(たとえば
Exception
)でキャッチすると、すべてのサブクラスを一括で捕捉できます。 - 特定のサブクラスだけをキャッチするようにすると、個別のエラー対処を行うこともできます。
- スーパークラス型(たとえば
例外クラスの定義例
実際に独自の例外クラスを定義する例を見てみましょう。たとえば「アプリで定義した ID が重複した場合」にスローする独自例外 DuplicateIdException
をチェック例外として作る場合です。
public class DuplicateIdException extends Exception { public DuplicateIdException() { super(); } public DuplicateIdException(String message) { super(message); } public DuplicateIdException(String message, Throwable cause) { super(message, cause); } }
Exception
を継承したことで、「チェック例外」として扱われます。- コンストラクタのオーバーロードにより、メッセージや原因(
cause
)を自由に渡せるようにしておくのが一般的です。 - この例外を投げるメソッドは、
throws DuplicateIdException
と宣言する必要があり、呼び出し側は必ずtry-catch
か、さらにthrows
の委譲をする必要があります。
JVM内部での例外の動作原理(スタックアンワインド)
Java のメソッド呼び出しは、JVM 上では「呼び出しスタック」という形で管理されています。
例外が投げられると、以下のようにスタックを遡っていきます。
- 例外オブジェクトが生成される
throw
キーワードや内部エラー検知でnew SomeException(...)
が呼ばれることで例外インスタンスが生成されます。
- 現在のメソッドが対応する
catch
を持っているか確認try-catch
ブロックがあるか、かつキャッチできる型かどうかをチェックします。
- なければ呼び出し元に遡る
- 現在のメソッドを抜けて、呼び出し元メソッドの
catch
があるかチェックします。 - これを再帰的に繰り返します。
- 現在のメソッドを抜けて、呼び出し元メソッドの
- 最終的に main メソッド(プログラムの起点)まで到達
- もし最後まで捕捉されなければ JVM の既定のエラーハンドラが受け取り、スタックトレース(どこでエラーが起きたかの情報)を表示してプログラムを終了します。