Javaのthrowとは?例外を投げる使い方と設計の判断軸
CONTENTS
しかし「Javaのthrowって何?」と聞かれると、例外を発生させる以上の説明で詰まることがあります。throwは単なる文法ではなく、異常系を“設計”として扱うための重要な道具です。さらに、入力チェックの失敗、到達不能な分岐、外部I/Oの失敗など、実務ではthrowの使い方次第で調査時間や保守性が大きく変わります。
そこで本記事では、throwの基本構文から、throwsとの違い、チェック例外・非チェック例外の考え方、そして「いつthrowすべきか」を判断できるように整理します。なお一次情報として、Oracle公式の例外解説やJLS(Java言語仕様)も参照できるようにしています。
throwの意味:例外を“その場で発生させる”
まず、throwは例外(Throwable)を投げて処理を中断し、呼び出し元へ伝播させる文です。Oracleのチュートリアルでも、throwは例外を投げるための文であり、引数にThrowableオブジェクトを取ると説明されています(公式)。
例えば、throwの最小形は次の通りです。なお、throwで投げられるのは Throwable とそのサブクラス(Exception や Error)です。
Oracle Java Tutorials: How to Throw Exceptions / Throwable (Java SE 8 API)
throw someThrowableObject;
そのため、throwの本質は「異常を知らせる」だけでなく、異常の種類・原因・責務の境界をコードで表現する点にあります。例えば、戻り値で-1などの“変な値”を返す設計は、呼び出し側がチェックし忘れると不具合になりがちです。一方で、言語仕様でもthrowによる明示的な例外は、そうした戻り値方式の弱点を避ける意義が述べられています。
Java Language Specification: Exceptions
throwの基本構文とよく使う例外
さらに実務では、throwは「自分で作った例外」だけでなく、標準例外を使う場面が多いです。特に入力検証では IllegalArgumentException が定番です。例えば、引数がnullや範囲外のときに、処理を継続すると不整合が広がるので、早期にthrowして止めます。
public static int parseAge(String value) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("age is required");
}
int age = Integer.parseInt(value);
if (age < 0) {
throw new IllegalArgumentException("age must be >= 0");
}
return age;
}
ただし、例外の選び方にはコツがあります。例えば「利用者が入力を間違えた」だけなのか、「呼び出し側の実装が間違っている(プログラマの誤り)」なのかで、例外型やメッセージの粒度が変わります。一般に IllegalArgumentException は“呼び出し側が直すべき不正な引数”を表すのに向いています。
また、例外メッセージは短くてもよいですが、何が不正で、期待値は何かが分かると運用が楽になります。例えば「age invalid」より「age must be >= 0」の方が一次調査が速いです。
throwとthrowsの違い:実行と宣言
一方で、throwと混同しやすいのがthrowsです。throwはその場で例外を投げる(実行)のに対し、throwsはこのメソッドが例外を投げうることを宣言します。つまり、throwは処理の中、throwsはメソッド宣言部に書きます。
public static String readText(java.nio.file.Path path) throws java.io.IOException {
return java.nio.file.Files.readString(path);
}
そのため、throwsは「呼び出し元でcatchする」か「さらに上位へthrowsで伝播させる」ことを促すサインになります。特に検査例外(checked exception)は、宣言またはcatchがないとコンパイルで止まるため、設計の意思決定が必要です。
ただし、throwsを書けば安全になるわけではありません。重要なのは「どの層が責任を持って例外を解釈し、利用者へどう返すか」です。例えば、Web APIならコントローラ層で例外をHTTPステータスへ変換する、バッチなら終了コードとログを整える、といった設計が求められます。
チェック例外と非チェック例外:いつthrowすべきか
さらに迷いやすいのが「チェック例外(checked)と非チェック例外(unchecked)のどちらをthrowすべきか」です。ここは“回復可能性”で考えると整理しやすいです。
- 呼び出し側が合理的に回復できる:チェック例外(例:ファイルがない、外部I/Oが失敗した)
- 呼び出し側の実装ミスに近い:非チェック例外(例:不正引数、前提違反)
例えば、業務的に「会員が存在しない」は回復可能なケースが多い一方で、「年齢が負の数」はほぼ入力や実装の誤りです。したがって、前者はドメイン例外(独自例外)として扱い、後者は IllegalArgumentException などで早期に止める、という分け方が実務では機能します。
また、独自例外を作るときは、例外名を“状態”で表すと読みやすくなります。例えば UserNotFoundException や PaymentRejectedException のように、ログを見た瞬間に原因の方向性が分かる名前が有効です。
実務で差がつくthrow設計:握りつぶさない・原因を残す
しかしthrowを使っても、運用が苦しくなる実装があります。代表例は「例外をcatchして、原因を捨てて別の例外をthrowする」パターンです。原因(cause)が消えると、スタックトレースから根本原因に辿れず、調査時間が跳ね上がります。
try {
externalCall();
} catch (java.io.IOException e) {
// 原因例外(e)を渡すことで、調査が容易になる
throw new IllegalStateException("external call failed", e);
}
そのため、例外を置き換えるときは、できるだけ原因例外をコンストラクタに渡し、メッセージには「何をして失敗したか」を残すのが基本です。さらに、ログには例外オブジェクトを渡してスタックトレースを出すことで、一次調査が速くなります。
また、throwは“到達不能”の表現にも使えます。例えばswitchのdefaultが本来ありえないなら、黙って進めるより例外で止めた方が安全です。ただし、利用者入力の揺れで起きうるなら、例外ではなくバリデーションで弾く方が親切です。
まとめ:throwは「異常系を設計する」ための道具

したがって、throwを理解するコツは「文法」ではなく「責務」と「回復可能性」で考えることです。入力検証は早期にthrow、回復可能な失敗はチェック例外や上位層での変換、そして例外の原因は握りつぶさずに残す。この3点を押さえるだけで、例外処理は一気に読みやすく、調査しやすくなります。
最後に、一次情報としてOracle公式のthrow解説と、JLSの例外章もあわせて確認すると理解が安定します。特にチームで例外設計のルールを作るなら、公式ドキュメントを共通言語にするのがおすすめです。
つまずいたら、現役エンジニアと一緒に解決しよう
この記事で扱った【Java】は、独学だと細部で詰まりやすい分野です。さらに理解を深めたい方は、Zerocodeで実務直結の学習を進めてみませんか。
コード添削・詰まり解消を丁寧にサポート。
Java/SQL/Spring Bootを体系的に習得。
現場で刺さる成果物づくりを伴走。
時間と場所を選ばず学べます。