JavaのServletで404・405・500エラーが出たら?原因と確認する場所

Servletが動かないときは、いきなりコード全体を書き直すのではなく、まず「どのURLへ、GETとPOSTのどちらで送って、何番の応答が返ったか」を確認します。404ならURLと配置、405なら送信方法と受け取り方、500ならサーバーログが、調査の出発点です。
たとえば「フォームからは動くのに、URLを直接開くと405になる」「数量に文字を入れると500になる」は、別々の原因です。この記事では、同じ注文金額の確認プログラムを使って、エラーを再現する操作、見る場所、修正、直ったかの確認までつなげます。
Schoo「Java入門 中級」第5回「サーバーサイドアプリケーションの実装基礎」の補足記事です。Java 17・Tomcat 10.1の jakarta.servlet を前提にしています。
404・405・500では、最初に確認する場所が違う
| 番号 | 意味の目安 | 最初に見るもの |
|---|---|---|
| 404 Not Found | 要求したリソースが見つからない | 実際に送ったURL、アプリの配置場所、ServletのURL設定 |
| 405 Method Not Allowed | そのURLで、そのHTTPメソッドを受け付けていない | GET/POSTと、doGet/doPostの対応 |
| 500 Internal Server Error | サーバー側で処理を完了できない問題が起きた | 同じ操作・時刻に対応するサーバーログ |
番号はHTTPステータスコードです。原因を一つに決めつけるものではありません。404が必ずつづり間違いとは限らず、500が必ず数値変換の失敗とも限りません。以下では代表的な原因を実際に再現します。なお、「接続できません」のようにHTTP応答自体を受け取れない場合は、まずTomcatの起動状態・ホスト・ポートを確認します。
イラスト:エラー番号から確認する場所を分ける

まずブラウザで「失敗したリクエスト」を特定する
- ブラウザの開発者ツールで Network(ネットワーク) を開く。
- フォーム送信など、問題が起きた操作をもう一度行う。
- 送信先のリクエストを選び、Headersの Request URL・Request Method・Status Code を確認する。
- POSTの入力内容はPayloadのForm Data、GETの入力内容はURLのクエリパラメータも確認する。
赤い行が複数あっても、最初に見るのは今回の操作の送信先です。たとえばfavicon.icoの404だけを見て、ServletのURLを直してはいけません。リダイレクトがある場合はログを保持し、最初のPOSTと、そのあとのGETを分けて確認します。
ブラウザのConsoleと、Tomcatのコンソールは別です。Javaの例外を探す場所は、Eclipseなどから起動したTomcatのコンソール、またはTomcatのサーバーログです。Networkは「何を送って何が返ったか」、サーバーログは「サーバー内で何が起きたか」を調べます。
再現に使うプログラム:1個500円の商品を計算する
数量3なら合計1500円を表示するだけのプログラムです。注文登録やDB更新はしません。画面遷移やJSPの問題を混ぜないため、結果はServletから短いテキストで返します。実際のアプリの画面構成は、別記事で扱ったServlet・Model・JSPの役割分担を使えます。
アプリのコンテキストパスを /lesson05 とします。自分の環境で違う名前なら、その部分だけ読み替えてください。次のServletは500を再現するため、入力チェックを省いたコードです。後半に修正版を掲載します。
入力画面:src/main/webapp/order-form.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>注文金額の確認</title>
</head>
<body>
<h1>注文金額の確認</h1>
<p>1個500円の商品です。注文の登録は行いません。</p>
<form action="order-preview" method="post">
<label for="quantity">数量(1〜99)</label>
<input id="quantity" name="quantity" type="text"
inputmode="numeric">
<button type="submit">金額を確認</button>
</form>
</body>
</html>
今回は文字入力で起きる問題も調べるため、type=”text”にしています。inputmode=”numeric”は数字を入力しやすいキーボードの指定であり、サーバー側の入力チェックの代わりにはなりません。
エラー再現用:src/main/java/example/OrderPreviewServlet.java
package example;
import java.io.IOException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/order-preview")
public class OrderPreviewServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
request.setCharacterEncoding("UTF-8");
String text = request.getParameter("quantity");
int quantity = Integer.parseInt(text);
int total = quantity * 500;
response.setContentType("text/plain; charset=UTF-8");
response.getWriter().println("合計: " + total + "円");
}
}
http://localhost:8080/lesson05/order-form.html を開き、まず3を入力します。「合計: 1500円」と表示され、Networkが POST /lesson05/order-preview → 200 なら基準の動作を確認できています。ここから変更する箇所は一度に一つにします。
404の例:フォームの送信先URLを間違えた
フォームのactionだけを、次のように変更します。preview の末尾を preveiw と取り違えています。
<form action="order-preveiw" method="post">
3を入力して送信すると、このサンプルでは404になります。確認するのは「Javaのクラス名とURLが同じか」ではなく、実際のURLと、コンテキストパス・@WebServletの組み合わせです。
| 確認する情報 | 今回の値 |
|---|---|
| NetworkのRequest URL | http://localhost:8080/lesson05/order-preveiw |
| アプリのコンテキストパス | /lesson05 |
| Servletの設定 | @WebServlet("/order-preview") |
| 正しい送信先 | /lesson05/order-preview |
フォームをアプリ直下のorder-form.htmlから開いているので、相対URLのaction=”order-preview”は /lesson05/order-preview になります。actionをorder-previewへ戻し、同じフォームからPOST送信して200になることを確認してください。OrderPreviewServletというクラス名を、そのままURLの末尾にするわけではありません。
つづりが正しくても404なら、次の順で切り分けます。
- 入力画面order-form.htmlも404なら、コンテキストパスとアプリのデプロイ・起動結果を確認する。Tomcat自体が動いていても、そのアプリの起動に失敗している場合がある。
- 入力画面は200でServletだけ404なら、@WebServletのパス、最新クラスの反映、アプリ起動時のエラーを確認する。コンパイルエラーが残っていないかも見る。
- Servletでの処理後に404なら、forward先のJSPなどが本当に存在するか、パスや大文字・小文字が合っているか確認する。
forward先のJSPが存在しない場合も404になることがあります。404だからServletの処理が一切動いていない、とまでは断定できません。ブレークポイントやサーバーログで、どこまで到達したかも確認します。
405の例:doPostしかないServletへGETを送った
actionを正しいorder-previewへ戻したうえで、フォームから method="post" を外して送信します。methodを省略したフォームはGETになるため、このサンプルでは405になります。ServletのURLをアドレスバーに直接入力する場合や、通常のリンクをクリックする場合もGETです。
<form action="order-preview">
| 確認する情報 | 今回の状態 |
|---|---|
| Request URL | /lesson05/order-preview?quantity=3 |
| Request Method | GET |
| Servletに書いた処理 | doPostだけ |
| 結果 | GETを処理するdoGetがないため405 |
修正は、今回決めた送信方法に合わせてフォームのmethod=”post”を戻すことです。URLを直接開くのではなく、入力画面から送信し直します。POSTで200になれば、Servletの登録先とPOST処理は動いていることが分かります。
検索画面など、もともとGETで受け付ける設計ならdoGetを実装するのが適切です。しかし、405を消す目的だけでdoGetからdoPostを呼ぶのは避けます。登録や削除までGETで実行できてしまう可能性があるためです。どちらを使うかは、処理の役割に合わせて決めます。
リダイレクト後だけ405になる場合も、移動先で実際にGETが送られていないかを確認します。POST専用のURLへリダイレクトするのではなく、表示用のGETを受け付けるURLへ進める構成を検討します。
500の例:数量にabcを入れて数値へ変換した
フォームのactionとmethodを元に戻し、数量に abc と入力して送信します。送信先は正しくPOSTにも対応していますが、エラー再現用コードの Integer.parseInt(text) でNumberFormatExceptionが起き、この例では500になります。
ブラウザに詳細な例外が表示されるかは、サーバーの設定によって異なります。500という画面だけでも、サーバー側のログを確認できます。Eclipseなら実行中のTomcatのコンソールを選びます。単独起動のTomcatなら、実行環境の CATALINA_BASE/logs 以下など、設定された出力先を確認します。ファイル名や出力先は起動方法によって変わります。
サーバーログの読み取り例(抜粋)
java.lang.NumberFormatException: For input string: "abc"
at java.base/java.lang.Integer.parseInt(...)
at example.OrderPreviewServlet.doPost(OrderPreviewServlet.java:17)
...
これは今回の例外から、読む箇所を抜き出した表示です。日時・Tomcat側の行やJDK内部の行番号は省略しています。17行目は、この記事のエラー再現用Javaファイルをそのまま使った場合の位置です。空行やimportを追加すれば行番号も変わります。
| ログの箇所 | 読み取ること | 次の確認 |
|---|---|---|
| NumberFormatException | 数値への変換に失敗した | どの値を変換しようとしたか |
| For input string: “abc” | abcを変換しようとした | フォームの入力とPayloadのquantity |
| OrderPreviewServlet.java:17 | 自分のファイルの17行目で例外が表に出ている | Integer.parseInt(text)の直前でtextを確認する |
最初に見つかったTomcat内部のファイルを直すわけではありません。例外の種類とメッセージを読み、自分のパッケージexampleの行へ進みます。Caused by: がある場合は、包まれた例外の原因もたどり、その部分にある自分のコードの行を確認します。
ブラウザのPayloadではquantity=abcになっているのか、そもそもquantityが送られていないのかも区別します。後者なら、HTMLのnameとgetParameterの名前が一致しているかを確認します。画面にある入力欄でも、nameが違えば期待した名前では取得できません。
修正版:入力の問題を500のままにしない
利用者がabcを入力する可能性はあります。そこで、数量が存在するか、整数にできるか、1〜99の範囲かを確認してから計算します。同じファイルの内容を次のコードへ置き換えます。再現用と修正版を同時に配置するのではありません。
package example;
import java.io.IOException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/order-preview")
public class OrderPreviewServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/plain; charset=UTF-8");
String text = request.getParameter("quantity");
if (text == null || text.isBlank()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("数量を入力してください。");
return;
}
int quantity;
try {
quantity = Integer.parseInt(text.trim());
} catch (NumberFormatException e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("数量は1〜99の整数で入力してください。");
return;
}
if (quantity < 1 || quantity > 99) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("数量は1〜99の整数で入力してください。");
return;
}
int total = quantity * 500;
response.getWriter().println("合計: " + total + "円");
}
}
ここでは入力不正に対し、400 Bad Requestと固定メッセージを返す設計にしています。setStatus()だけではメソッドは終わらないため、returnで後続の計算を止めます。tryの範囲は数値変換だけにし、予想したNumberFormatExceptionを扱っています。
400はこのサンプルで選んだ入力エラーの応答です。入力JSPへエラーを渡して再表示する設計もあります。大切なのは、想定できる入力不正を、原因不明の500として放置しないことです。一方、何でも catch (Exception e) で囲んで200を返すと、本当の不具合まで成功に見えてしまいます。
HTML側にrequiredやtype=”number”を追加しても、サーバーへ別の方法でリクエストは送れます。サーバー側の検証は残してください。この例は入力値をそのままHTMLへ埋め込まず、固定メッセージと検証後の数値だけをテキストで返しています。
修正後の確認表:正常系だけで終わらせない
| 操作 | エラー再現用 | 修正版 |
|---|---|---|
| 正しいURLへPOST、数量3 | 200・合計1500円 | 200・合計1500円 |
| 正しいURLへPOST、数量abc | 500 | 400・整数の入力を案内 |
| 正しいURLへPOST、数量が空 | 500 | 400・数量の入力を案内 |
| 正しいURLへPOST、数量0/100 | 200・範囲外も計算してしまう | 400・1〜99を案内 |
| 正しいURLへGET | 405 | 405(POST専用の設計は同じ) |
| 誤ったorder-preveiwへPOST | 404 | 404(URLの誤りは別途直す) |
入力チェックを追加しても、間違ったURLやHTTPメソッドは直りません。「どの変更で、どの失敗が直るのか」を分けて確認します。逆に、200でも数量0を受け付けていた再現用コードのように、業務上の要件を満たしていない場合があります。番号だけでなく表示結果も確認してください。
500の原因が数値変換ではなかった場合
| ログの手がかり | 確認する内容 |
|---|---|
| NullPointerException | nullのまま使った変数は何か。パラメータやrequest属性の名前、取得できない経路を確認する。 |
| JSPのコンパイルエラー・JasperException | ログに示されたJSPの行、import、Javaの構文や型を確認する。JSPもサーバー側で処理される。 |
| SQLExceptionなどDB関連の例外 | 原因メッセージ、接続先、SQL、接続障害を確認する。入力変換とは切り分ける。 |
これらも未処理のまま伝われば500の原因になり得ますが、アプリ側の例外処理や応答状態によって表示は変わります。「500だからこの修正」と決めず、同じ操作のログに出た事実から調べます。修正後はビルド・デプロイが成功していることを確認し、同じ入力で再試験します。
調査結果を残すときの書き方
操作: order-form.htmlで数量abcを入力し送信
リクエスト: POST /lesson05/order-preview
結果: HTTP 500
例外: NumberFormatException
位置: OrderPreviewServlet.java:17
原因: 数値でない文字列を検証せずparseIntへ渡していた
修正: 未入力・変換失敗・範囲外を分けて検証
再確認: 数量abcは400、数量3は200・合計1500円
「動きません」だけでなく、この情報があると自分でも他の人でも追いやすくなります。ただし、ログや画面を共有するときはパスワード・Cookie・セッションID・個人情報などを除いてください。公開環境では、内部の例外詳細やファイル構成を利用者へそのまま表示せず、サーバー側で調査できるようにします。
確認問題
アプリは /lesson05、Servletの設定は @WebServlet(“/order-preview”)、doPostのみとします。修正版の入力仕様は数量1〜99です。
- フォームが
POST /lesson05/OrderPreviewServletを送って404になりました。どの情報を比較し、送信先をどう直しますか。 - フォームからPOSTすると200ですが、
/lesson05/order-preview?quantity=3をアドレスバーから開くと405です。原因と、このサンプルでの確認方法を答えてください。 - HTMLが
name="count"、JavaがgetParameter("quantity")になっています。値3を入れたのに、エラー再現用コードではNumberFormatExceptionによる500でした。何を確認し、どこを修正しますか。 - 修正版で、数量abcと数量3をPOSTしたとき、それぞれ何番とどの表示を期待しますか。GETで405が残ることは、この修正の失敗でしょうか。
演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。
まとめ
- 失敗したリクエストのURL・HTTPメソッド・ステータスをNetworkで確認する。
- 404はURLと配置、405はGET/POSTの対応、500はサーバーログから調査を始める。
- 例外の種類・原因メッセージ・自分のコードの行を対応させる。
- 原因を一つずつ修正し、同じ入力と正常な入力の両方で確認する。
- エラー番号だけで原因や修正成功を断定しない。