@ExceptionHandlerとは?Springのエラーハンドリングを優しく解説【初心者向け】
新人
「先輩、Springで画面が突然真っ白になって、500エラーって出たんですが、これってどういう意味なんですか?」
先輩
「それはアプリケーションの中で何かしらの例外(エラー)が発生して、それを適切に処理していない状態だね。Springでは、@ExceptionHandlerを使うことで、例外をキャッチして見やすいエラーメッセージに切り替えることができるんだよ。」
新人
「なるほど……でも、例外ってどんなときに出るんですか?そしてどうやってその@ExceptionHandlerって使うんですか?」
先輩
「じゃあまず、Springの@ExceptionHandlerとは何か、そしてなぜ必要なのかを順番に説明していこうか。」
1. @ExceptionHandlerとは?
Spring Frameworkにおける@ExceptionHandlerとは、特定の例外が発生したときに、それを受け取って独自の処理を行うためのアノテーションです。主に@Controllerクラス内で使われ、例外が発生した際に適切なビューやメッセージを返すことができます。
例えば、ユーザーが存在しないIDでアクセスしたときにエラー画面ではなく「ユーザーが見つかりません」と表示したい場合、この@ExceptionHandlerが活躍します。
以下は簡単なコード例です。
@Controller
public class UserController {
@GetMapping("/user/{id}")
public String getUser(@PathVariable("id") int id, Model model) {
if (id != 1) {
throw new UserNotFoundException("ユーザーが見つかりません");
}
model.addAttribute("name", "山田太郎");
return "user";
}
@ExceptionHandler(UserNotFoundException.class)
public String handleUserNotFound(UserNotFoundException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/user-not-found";
}
}
このようにhandleUserNotFoundメソッドは、UserNotFoundExceptionが発生したときに呼び出され、エラーメッセージを画面に表示する役割を果たします。
2. なぜエラーハンドリングが重要なのか?
Webアプリケーションでは、ユーザーの操作によって意図しない例外(エラー)が発生することがあります。たとえば、存在しないURLにアクセスしたり、不正なフォーム入力を送信したりしたときです。こういったときにエラー処理をしっかりしていないと、
- 真っ白な画面が表示される
- ブラウザに500エラーが出る
- 内部の情報(スタックトレースなど)が見えてしまう
といった問題が発生します。特にHTTPステータスコード 500は、サーバー内部で処理に失敗したことを示しており、ユーザーからすると「壊れてる?」という印象を与えてしまいます。
Springの@ExceptionHandlerを使えば、例外ごとに適切なレスポンスを返すことができ、「Java 例外 処理」や「Spring エラーハンドリング」を学びたい初心者にとっても、保守性の高いコードを書く第一歩となります。
例えば、入力ミスが原因のIllegalArgumentExceptionなども、次のように丁寧に処理できます。
@Controller
public class FormController {
@PostMapping("/submit")
public String submit(@RequestParam("age") int age) {
if (age < 0) {
throw new IllegalArgumentException("年齢は0以上で入力してください");
}
return "success";
}
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgument(IllegalArgumentException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/input-error";
}
}
このように、ユーザーに対して分かりやすく丁寧なメッセージを返すことができるのが@ExceptionHandlerとは何かを理解するポイントです。
3. @ExceptionHandlerを使った基本的なサンプルコード
実際の現場では、NullPointerExceptionのような「うっかりミス」で発生する例外も少なくありません。このような例外に対しても、@ExceptionHandlerを使って丁寧に対応することで、エラー画面にユーザーフレンドリーなメッセージを表示できます。
以下は、NullPointerExceptionに対応する@ExceptionHandlerの使い方です。Thymeleafテンプレートにエラーメッセージを渡して、専用の画面を表示する構成です。
@Controller
public class SampleController {
@GetMapping("/sample")
public String sample(Model model) {
String message = null;
// この行でNullPointerExceptionが発生する
int length = message.length();
model.addAttribute("length", length);
return "result";
}
// NullPointerExceptionを捕捉する
@ExceptionHandler(NullPointerException.class)
public String handleNullPointer(NullPointerException ex, Model model) {
model.addAttribute("errorMessage", "処理中に予期しないエラーが発生しました(NullPointerException)");
return "error/null-error";
}
}
このように、「Java NullPointerException 対処法」を知っておくことで、予期せぬエラーによるアプリケーションのクラッシュを防げます。初心者でもコピーしてそのまま使えるシンプルなサンプルです。
4. 複数の例外を個別に処理する方法
現実のアプリケーション開発では、IllegalArgumentExceptionやIOExceptionなど、複数の例外が発生する可能性があります。それぞれの例外を@ExceptionHandlerで個別に分けて処理することで、より適切な対応ができます。
ここでは、フォームの入力値に不正がある場合と、ファイル操作でエラーが発生する場合の例を紹介します。
@Controller
public class FileUploadController {
@PostMapping("/upload")
public String upload(@RequestParam("filename") String filename, Model model) throws IOException {
if (filename == null || filename.isBlank()) {
throw new IllegalArgumentException("ファイル名を入力してください");
}
// ダミーのIOExceptionを強制的に発生させる
if (filename.equals("error.txt")) {
throw new IOException("ファイルの読み込みに失敗しました");
}
model.addAttribute("message", "アップロード成功");
return "upload-success";
}
// 入力チェックの例外
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArg(IllegalArgumentException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/input-error";
}
// ファイル操作の例外
@ExceptionHandler(IOException.class)
public String handleIOException(IOException ex, Model model) {
model.addAttribute("errorMessage", "ファイルの処理中にエラーが発生しました");
return "error/io-error";
}
}
このように、「Spring エラー 例外処理」の基本として、例外の種類ごとにエラーメッセージや遷移先のテンプレートを分けることで、ユーザーの操作ミスとシステム的な問題を切り分けやすくなります。
5. モデルにメッセージを渡してエラーページを表示する方法
@ExceptionHandlerでは、単に例外を処理するだけでなく、Modelに値を渡すことで、Thymeleafテンプレートに具体的なエラー内容を表示することができます。
たとえば、以下のようにエラー詳細をerrorMessageとして渡せば、テンプレート側でその値を表示することができます。
@Controller
public class ProductController {
@GetMapping("/product/{id}")
public String getProduct(@PathVariable("id") int id, Model model) {
if (id <= 0) {
throw new IllegalArgumentException("無効な商品IDです");
}
model.addAttribute("productName", "商品A");
return "product-detail";
}
@ExceptionHandler(IllegalArgumentException.class)
public String handleInvalidProductId(IllegalArgumentException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/product-error";
}
}
次に、ThymeleafのテンプレートでerrorMessageを表示する基本的な方法も紹介します。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>エラー</title>
</head>
<body>
<h1>エラーが発生しました</h1>
<p th:text="${errorMessage}">エラーメッセージがここに表示されます</p>
<a th:href="@{/}">トップページへ戻る</a>
</body>
</html>
このように、Modelを通じて動的なエラーメッセージを表示できるため、ユーザーに対して優しいインターフェースを提供することができます。
@ExceptionHandler 使い方として、実際のエラー画面と連携する方法も押さえておきましょう。
6. @ExceptionHandlerが使われないとどうなるか
もし@ExceptionHandlerによる例外処理を実装していない場合、Springアプリケーションでは例外がコントローラー外に伝播し、デフォルトのエラーページが表示されます。これがいわゆる「HTTPステータスコード500エラー」で、ユーザーにとっては突然のエラー画面となり、不安を与える原因になります。
例えば、次のように例外が発生しても、@ExceptionHandlerを定義していなければ、ブラウザには以下のような画面が表示されます。
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
There was an unexpected error (type=Internal Server Error, status=500).
java.lang.NullPointerException
この画面は、Spring Bootの初期状態ではよく見られるもので、内部情報(例外名やスタックトレース)がそのまま表示されてしまうため、セキュリティ上も問題があります。
このような状況を防ぐために、「Spring 500エラー 解決法」として、適切な@ExceptionHandlerの実装が不可欠です。
7. Spring全体に共通のエラーハンドリングをするには
各@Controllerごとに@ExceptionHandlerを記述するのは、小規模なアプリケーションでは問題ありませんが、大規模になると管理が煩雑になります。
そこで、Springでは複数のコントローラーに共通する例外処理を一元化するための仕組みとして@ControllerAdviceを提供しています。
@ControllerAdviceは、全ての@Controllerに共通の@ExceptionHandlerを定義できる特別なクラスです。以下に、全体設計における「@ExceptionHandlerと@ControllerAdvice 違い」の具体例を紹介します。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgument(IllegalArgumentException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/global-input-error";
}
@ExceptionHandler(NullPointerException.class)
public String handleNullPointer(NullPointerException ex, Model model) {
model.addAttribute("errorMessage", "システム内部でエラーが発生しました(NullPointerException)");
return "error/global-null-error";
}
}
このように@ControllerAdviceを使うことで、どの@Controllerから例外が発生しても共通の処理を行うことができ、「Spring 例外処理 全体設計」の基本として知っておくべき機能です。
なお、特定のパッケージ配下の@Controllerに限定して処理を適用したい場合は、次のようにbasePackages属性を使います。
@ControllerAdvice(basePackages = "com.example.controller")
public class LimitedScopeExceptionHandler {
// 処理内容は同じ
}
8. エラーハンドリングのまとめと注意点
最後に、エラーハンドリングを設計する際の注意点やポイントを整理しておきましょう。
■ ログをしっかり残すこと
例外が発生した際は、ユーザーには丁寧なメッセージを表示しつつ、開発者向けには詳細な情報をログに出力することが重要です。以下はその一例です。
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public String handleAll(Exception ex, Model model) {
logger.error("予期せぬエラーが発生しました", ex);
model.addAttribute("errorMessage", "予期せぬエラーが発生しました。管理者に連絡してください。");
return "error/global-error";
}
■ ユーザーフレンドリーな画面を意識する
エラー画面もアプリケーションの一部です。システム用語や英語ばかりのエラー画面ではなく、誰でも理解できる日本語での説明と、トップページに戻れるリンクなどを用意しておくことが望ましいです。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>エラー画面</title>
</head>
<body>
<h1>エラーが発生しました</h1>
<p th:text="${errorMessage}">不明なエラーです</p>
<a th:href="@{/}">トップページに戻る</a>
</body>
</html>
■ テンプレートのパスミスに注意
例外を処理しても、返すテンプレートのパスが間違っていると、さらに別のエラー(404や500)が発生します。テンプレートファイルがsrc/main/resources/templates/error/に存在することを確認しましょう。
■ 例外クラスを明示的に設計する
独自の例外クラスを定義することで、より意味のあるエラーハンドリングが可能になります。たとえば、ユーザーが存在しない場合など、特定のドメインエラーを明確にするために例外クラスを設けるのが有効です。
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
このように、「Spring 例外処理 全体設計」を意識して、丁寧な設計と実装を心がけることで、使いやすく信頼性のあるアプリケーションが実現できます。