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

例えばほとんどのプログラミング言語に共通する事象として「0で割り算をしようとするとエラーが発生する」というものがあります。これを例外処理なしで実行すると、プログラムはそこで止まってしまいます。
が、しかし例外処理を使えばこのエラーを検知して「エラーが発生しました」というメッセージを表示し、その後のプログラムを続行することができるようになります。
0で割り算をする、という以外にも自分で新たな例外を作成することで、例えば「年齢は0以上でなければならない」というルールを守らない入力があった場合に、専用のエラーメッセージを表示することができるようにしたりすることができます。
このページでは、そもそも例外とは何か?そして例外処理をJavaで実現する方法(try-catch構文)の使い方やコツ・注意点を初心者向けにわかりやすく解説します。
例外とは?
try-catch構文の解説の前に、まずは例外とは何か?を正確に理解しておくことが重要です!まずは例外って何?というのを事前に丁寧に解説しておきます。
で、結論から言うと、例外とはJavaプログラムの処理中に異常が起きたことを示す「通知(オブジェクト)」です。Javaの世界では「予期せぬ異常」が発生すると、その状況を表す「例外クラス(オブジェクト)」が『投げられ』ます(throw)。
- 例外が発生すると?
- プログラムはそこで通常の処理を中断。
- 「例外(Exception)という名前のオブジェクト」を投げて異常を通知します。
- 誰に通知されるのか?
- この「例外」を受け取る仕組みが
catch文で、プログラムはそこで処理を再開できます。
- この「例外」を受け取る仕組みが

簡単なイメージを表すと、例外(ArithmeticException)が起きて「通知(オブジェクト)」が投げられ、それをcatchが受け取って処理するイメージ。
try {
int result = 10 / 0; // ゼロで割ると異常発生!
} catch (ArithmeticException e) { // 異常をキャッチ!
System.out.println("ゼロで割り算はできません!"); // 対応処理
}
例外はオブジェクトとして表現される、というのが重要ポイントです。そのうえで、このオブジェクトはどのようなクラス構造になっているか?も基本知識として知っておくべき重要ポイントです。
例外クラスの構造(Throwableを頂点とした階層構造)
Javaの例外は、クラス階層として見ると以下のような構造になっています。
java.lang.Object
└─ java.lang.Throwable
├─ java.lang.Error
└─ java.lang.Exception
└─ java.lang.RuntimeException
└─ ...
ポイント1 Throwable
- Java で「投げられる(throw できる)もの」を表す最上位のクラスです。
ErrorとExceptionの2系統に大きく分かれます。
Error
- JVM の致命的なエラー(例:
OutOfMemoryErrorやStackOverflowError)が含まれます。 - 通常のアプリケーションでは捕捉しても対処が難しいため、ほとんどの場合は積極的にcatchしません。
Exception
- アプリケーションで処理可能な問題が含まれます。
IOException、SQLException、ClassNotFoundExceptionなどが代表例です。
RuntimeException
Exceptionのサブクラスですが、RuntimeExceptionやそのサブクラスは「実行時例外」として扱われます(例:NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentExceptionなど)。- チェック例外(後述)と異なり、コンパイラが
try-catchやthrows宣言を強要しない性質を持ちます。

要するに、例外ってのは何かしらの異常を検知したときに投げられる「クラス」だよ!ってことと上記の構造を理解できていればまずはOK。そのうえで、チェック例外と非チェック例外についても解説しておきます。
チェック例外と非チェック例外(実行時例外)

チェック例外か非チェック例外か?
- 「発生する可能性が高い(外部リソースなど)」⇒ チェック例外
- 「開発時に防げるもの(バグやプログラムミス)」⇒ 非チェック例外
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)でキャッチすると、すべてのサブクラスを一括で捕捉できます。 - 特定のサブクラスだけをキャッチするようにすると、個別のエラー対処を行うこともできます。
- スーパークラス型(たとえば
ポイント 代表的な例外クラス
| 分類 | クラス名 | チェック例外か | 用途・意味 |
|---|---|---|---|
Error系 | OutOfMemoryError | Unchecked | メモリ不足時に発生 |
StackOverflowError | Unchecked | 再帰のしすぎなどでスタック領域が溢れる | |
Exception系 | RuntimeException | Unchecked | 実行時に発生する一般的な例外の親クラス |
└─ ArithmeticException | Unchecked | 数学演算時のエラー(例:0で割る) | |
└─ NullPointerException | Unchecked | nullを参照した操作を行った場合 | |
└─ ArrayIndexOutOfBoundsException | Unchecked | 配列の範囲外アクセス | |
└─ StringIndexOutOfBoundsException | Unchecked | 文字列のインデックス範囲外 | |
└─ NumberFormatException | Unchecked | 数字変換に失敗(例:Integer.parseInt("abc")) | |
└─ ClassCastException | Unchecked | 不正な型キャストをしたとき | |
└─ IllegalArgumentException | Unchecked | 不正な引数が渡されたとき | |
Exception系 | IOException | Checked | 入出力エラーの基本 |
└─ FileNotFoundException | Checked | ファイルが見つからない | |
SQLException | Checked | SQL実行時のエラー | |
ParseException | Checked | 日付などの解析処理失敗 | |
InterruptedException | Checked | スレッド処理で割り込みが発生 | |
ReflectiveOperationException | Checked | リフレクションAPI使用時の基底例外 |
例外クラスの定義例
実際に独自の例外クラスを定義する例を見てみましょう。たとえば「アプリで定義した 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 の既定のエラーハンドラが受け取り、スタックトレース(どこでエラーが起きたかの情報)を表示してプログラムを終了します。
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の標準例外クラスを継承して作成する独自の例外クラスです。これにより、特定のビジネスロジックやアプリケーションの要件に応じたエラー処理を行うことができます。例えば、年齢が負の値になってはいけないというルールを守らない入力があった場合に、専用のエラーメッセージを提供するためにカスタム例外を作成します。
カスタム例外の作成
- カスタム例外クラスの定義
- 標準の例外クラス(例:
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 が例外を受け取ってアプリケーションを終了する- スタックトレース(どのメソッドで例外が発生したか)が標準エラーに出力される。
このように、スタック上の各メソッドフレームを「巻き戻し」しながら、例外を補足できるブロックを探していく仕組みがスタックの巻き戻しです。
