Springで500エラー(サーバーエラー)の原因と対処方法
新人
「Spring Bootでアプリケーションを作ってたら、ブラウザに『500 Internal Server Error』って出てきたんですが、これって何なんですか?」
先輩
「それはいわゆるサーバーエラーってやつだね。Spring 500エラーとも呼ばれてて、サーバー側で何か問題が起きたときに出るエラーだよ。」
新人
「クライアント側じゃなくて、こっちのコードに問題があるってことですか?」
先輩
「そのとおり。たとえば、Javaの例外が発生して処理が失敗したり、コントローラの処理にバグがあったりすると、Springが500エラーを返すんだ。」
新人
「なるほど…。それじゃあ、どんなときに500エラーが出るのか、詳しく教えてもらえますか?」
先輩
「もちろん。まずは基本から見ていこうか!」
1. 500エラーとは?
500エラーは「Internal Server Error(内部サーバーエラー)」と呼ばれ、サーバー側で予期しない問題が発生したときに返されるHTTPステータスコードです。
これは、HTMLページの表示中にサーバーアプリケーション(ここではSpring)がエラーを検知し、ユーザーにエラーメッセージを返す状況です。通常、Javaの例外がキャッチされずにそのままスローされると、Springは500エラーとして処理します。
たとえば、数値を0で割ったり、nullの値にアクセスしてしまったりする場合などが該当します。これらはコード上のミスであり、アプリケーション開発者が対応すべき問題です。
500エラーが発生すると、ブラウザには次のようなメッセージが表示されます。
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).
これはSpring Bootのデフォルト動作で、エラーに対する専用のページが用意されていない場合に表示されます。ユーザーにとっては不親切なので、後ほどカスタマイズする方法も紹介していきます。
2. なぜ500エラーが発生するのか?
500エラーが発生する主な原因は、Java例外の発生です。特に、NullPointerExceptionやArrayIndexOutOfBoundsExceptionなどの実行時エラーは頻出です。
たとえば、以下のようなコードをSpringの@Controller内に書いたとします。
@Controller
public class SampleController {
@GetMapping("/test")
public String test500() {
String message = null;
int length = message.length(); // ← NullPointerExceptionが発生
return "test";
}
}
この場合、messageがnullなので、length()を呼び出したタイミングでNullPointerExceptionが発生します。
結果として、Springはこのエラーを処理できず、そのまま500 Internal Server Errorを返します。
他にも、たとえば以下のような例外も500エラーの原因になります。
- データベース接続エラー(
DataAccessException) - ファイル読み込みエラー(
IOException) - 不正なキャスト(
ClassCastException) - 変数の初期化忘れ
さらに、Gradleで依存関係が不足していてクラスが見つからない場合や、Thymeleafテンプレートのファイル名が間違っている場合なども、Springは500エラーとして扱います。
たとえば以下のように、存在しないテンプレートを指定しているとエラーになります。
@GetMapping("/error-sample")
public String errorView() {
return "notfound-template"; // resources/templates/notfound-template.html が存在しない
}
このとき、コンソールには次のようなスタックトレースが表示されることがあります。
org.thymeleaf.exceptions.TemplateInputException: Error resolving template [notfound-template]
このように、500エラーはさまざまな原因で発生しますが、どれも共通して「サーバーサイドで処理がうまくいかなかった」ということを表しています。
しっかりとエラーメッセージを確認し、コードのどこで問題が起きているかを特定することが重要です。
3. try-catchを使った例外処理の基本
Spring 500エラー 対処法の第一歩として、Javaの基本的な例外処理であるtry-catchを使う方法があります。
これは、エラーが発生しそうな処理をtryブロックで囲み、問題が起きたときの対応をcatchブロックに書く方法です。こうすることで、例外が発生しても500 Internal Server Errorにならずに、別のページを表示したり、メッセージを表示したりすることができます。
@Controller
public class TryCatchController {
@GetMapping("/try-catch")
public String tryCatchExample(Model model) {
try {
String message = null;
int length = message.length(); // NullPointerExceptionが発生する
} catch (NullPointerException e) {
model.addAttribute("errorMessage", "ヌルの値にアクセスしようとしました。");
return "error/handle"; // error/handle.html を表示
}
return "success";
}
}
このようにtry-catchを使えば、処理中にエラーが発生してもアプリケーション全体が落ちることはなく、ユーザーにやさしい対応が可能になります。
ただし、この方法はあくまで「その場の例外を処理する」ものであり、全体的なエラーハンドリングには向いていません。そこで、よりスケーラブルな方法として次に紹介する@ExceptionHandlerが活躍します。
4. @ExceptionHandlerでのエラー処理方法
Springでは、特定のコントローラ内で発生した例外を@ExceptionHandlerで処理することができます。これにより、try-catchを毎回書かなくても、まとめて例外を扱えるようになります。
@ExceptionHandler 使い方としては、対象の@Controllerクラス内にメソッドを用意し、処理したい例外クラスを引数に取るだけです。
@Controller
public class ExceptionHandlerController {
@GetMapping("/exception")
public String causeException() {
String text = null;
return text.toString(); // NullPointerExceptionが発生
}
@ExceptionHandler(NullPointerException.class)
public String handleNullPointerException(NullPointerException ex, Model model) {
model.addAttribute("errorMessage", "内部エラーが発生しました(ヌルポインタ)");
return "error/handle";
}
}
このコードでは、/exceptionにアクセスしてNullPointerExceptionが発生すると、自動的にhandleNullPointerExceptionメソッドが呼ばれます。
これによって、Spring 500エラー 対処法として、例外をユーザー向けのメッセージに置き換えることができます。
ただし、@ExceptionHandlerはその@Controller内だけで有効です。複数のコントローラで同じような処理をする場合、冗長になってしまいます。そこで活躍するのが、@ControllerAdviceです。
5. @ControllerAdviceを使った全体的なエラーハンドリング
@ControllerAdvice エラー処理は、アプリケーション全体で共通のエラーハンドリングを行う方法です。@ControllerAdviceを使えば、どの@Controllerで発生した例外もまとめて処理できます。
これにより、アプリケーション全体の500エラーやその他の例外に対して、統一した対応をとることができます。
package com.example.demo.advice;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.stereotype.Controller;
@ControllerAdvice(annotations = Controller.class)
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleGlobalException(Exception ex, Model model) {
model.addAttribute("errorMessage", "予期しないサーバーエラーが発生しました。");
return "error/global";
}
}
この例では、Exceptionをキャッチすることで、すべての例外を処理対象としています。もちろん、必要に応じてNullPointerExceptionやIllegalArgumentExceptionなど、個別の例外を指定することもできます。
また、この仕組みを有効にするためには、Thymeleafのテンプレートファイル(例:resources/templates/error/global.html)を用意しておく必要があります。
このテンプレートには、ユーザーにわかりやすいメッセージや、トップページへのリンクなどを記載するとよいでしょう。
<!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>
このように@ControllerAdviceを使うことで、エラー処理を一か所に集約し、コードの重複を減らし、保守性を高めることができます。
Spring 500エラー 対処法としては非常に強力で、特に複数のコントローラを持つ大規模なプロジェクトでは必須の設計とも言えるでしょう。
6. よくある500エラーの実例とその原因
ここでは、実際の開発中に発生しやすい500エラー 原因 特定のために、代表的な例を紹介し、それぞれの原因を丁寧に解説します。
例1:NullPointerException
もっとも頻繁に見られるエラーのひとつが、NullPointerExceptionです。これは、nullのオブジェクトに対してメソッドやプロパティを呼び出そうとした場合に発生します。
@GetMapping("/null")
public String nullError() {
String value = null;
return value.toString(); // NullPointerException
}
このようなケースでは、nullチェックを忘れていたことが原因です。
例2:配列の範囲外アクセス
ArrayIndexOutOfBoundsExceptionは、配列やリストの範囲外にアクセスしたときに発生します。
@GetMapping("/array")
public String arrayError() {
int[] numbers = {1, 2, 3};
int value = numbers[5]; // 配列のインデックスエラー
return "result";
}
配列やリストのサイズを超えるインデックスにアクセスしていないか確認しましょう。
例3:ファイル読み込み時のIOException
ファイルを扱う際はIOExceptionが発生する可能性があります。たとえば存在しないファイルを開こうとした場合です。
@GetMapping("/file")
public String fileError() throws IOException {
FileReader reader = new FileReader("notfound.txt");
reader.read();
return "result";
}
例外の種類を意識して、適切な例外処理を行うことで、500エラー 原因 特定がしやすくなります。
7. エラー時のログ出力方法と見方
Java ログ 出力 方法を理解しておくことは、エラーの原因を素早く特定するために重要です。
Spring Bootでは、標準でログ出力(logging)の仕組みが組み込まれており、アプリケーション実行中のエラーは自動的にログに出力されます。
コンソールには次のようなスタックトレースが表示されます。
java.lang.NullPointerException: Cannot invoke "String.length()" because "message" is null
at com.example.demo.controller.SampleController.test(SampleController.java:14)
この出力の見方としては、次の点を確認します。
- 例外の種類:NullPointerExceptionなど
- 発生箇所:どのクラスの何行目でエラーが出たか
- メッセージ:「nullのままメソッドを呼び出している」などの内容
ログの出力レベルは、application.propertiesで制御できます。
logging.level.org.springframework=INFO
logging.level.com.example.demo=DEBUG
自分のアプリケーションパッケージ(例:com.example.demo)に対してDEBUGを指定しておけば、より詳細なログが確認でき、Spring エラー デバッグがスムーズに進みます。
8. 開発中にエラーを早期発見するための工夫
Spring エラー デバッグをしっかり行うためには、エラーを事前に防いだり、早期に気づくための工夫が必要です。
① デバッグ機能を使う
Pleiades環境では、EclipseベースのIDEにより、ブレークポイントを設置して変数の中身を確認することが可能です。
たとえば、次のようにしてメソッドにブレークポイントを設定し、変数がnullかどうかを実行中に確認できます。
- コントローラクラスを開く
return文の前にブレークポイントを置く- 「デバッグ」モードでアプリケーションを起動する
② 単体テストを活用する
例外が起きやすいメソッドには、JUnitなどを使ってテストを書くことが推奨されます。
これにより、開発中の段階でバグに気づき、Spring 500エラーを未然に防ぐことができます。
@SpringBootTest
public class SampleTest {
@Test
void testNull() {
String value = null;
assertThrows(NullPointerException.class, () -> {
value.length();
});
}
}
③ 意識してログ出力を行う
重要な処理の前後や、外部とのやり取り(データベース・ファイル読み込みなど)では、ログを出力することでエラー箇所の特定がしやすくなります。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Controller
public class LogController {
private static final Logger logger = LoggerFactory.getLogger(LogController.class);
@GetMapping("/log")
public String logExample() {
logger.info("処理を開始します");
// 処理
logger.info("処理が完了しました");
return "result";
}
}
このようにして、処理の流れを追跡できるようにしておくと、予期しない挙動があったときにすぐに気づくことができます。
まとめ
今回紹介したように、500エラー 原因 特定には例外の種類と発生箇所の把握が欠かせません。そのためには、ログ出力やtry-catch、@ExceptionHandler、@ControllerAdviceを使った仕組みを適切に組み合わせて、効率よくデバッグできる環境を整えることが大切です。