Spring Bootの400エラーと500エラーの違いを新人エンジニア向けにやさしく解説
こんにちは。ゆうせいです。
今回は、Spring Bootでよく見る400エラーと500エラーの違いについて解説します。
Webアプリを開発していると、画面やAPIで次のような表示を見ることがあります。
400 Bad Request
500 Internal Server Error
どちらもエラーですが、原因の場所が違います。
ざっくり言うと、400エラーは「リクエストした側の内容が悪い」、500エラーは「サーバー側の処理で問題が起きた」と考えるとわかりやすいです。
| ステータス | 意味 | 原因の場所 | 新人向けのイメージ |
|---|---|---|---|
| 400 Bad Request | リクエストが不正 | ブラウザ、フォーム、APIリクエスト側 | 注文用紙の書き方が間違っている |
| 500 Internal Server Error | サーバー内部エラー | Spring Boot、Java、DB、サーバー側 | 厨房の中で料理中に問題が起きた |
Spring FrameworkのHttpStatusでも、4xxはClient Error、5xxはServer Errorの系列として扱われます。つまり、400番台はクライアント側のリクエストに問題があるエラー、500番台はサーバー側で処理に失敗したエラーとして分類されます。{index=0}
400エラーとは何か
400エラーは、正式にはBad Requestです。
Bad Requestは、日本語にすると「悪いリクエスト」「不正なリクエスト」という意味です。
Spring Bootで400エラーが出るときは、Controllerに届いたリクエストの形が、Spring Boot側の期待と合っていないことが多いです。
たとえば、Controllerが「数値を送ってください」と待っているのに、画面から「abc」のような文字列が送られた場合です。
レストランでたとえるなら、注文用紙に「カレー 1個」と書くべきところに、「カレー たくさん」と書いてしまったようなものです。
厨房は処理を始める前に、「この注文は形式が合っていません」と判断します。
Spring Bootで400エラーが起きやすい場面
| 原因 | 例 | なぜ400になるか |
|---|---|---|
| 数値に変換できない | age=abc | intやIntegerに変換できないため |
| 必須パラメータがない | nameが必要なのに送っていない | @RequestParamの必須値が不足しているため |
| バリデーションエラー | 必須入力が空、文字数オーバー | @Validや入力チェックに失敗したため |
| JSONの形式が壊れている | { "name": "田中", } | JSONとして読み取れないため |
| 日付形式が合わない | 2026/99/99 | LocalDateなどに変換できないため |
400エラーのポイントは、Spring Bootが「処理を続ける前に、入力値がおかしい」と判断している点です。
400エラーのコード例
次のControllerを見てください。
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SampleController {
@GetMapping("/sample")
public String sample(@RequestParam int age) {
return "年齢は " + age + " 歳です。";
}
}このControllerは、ageというリクエストパラメータをint型で受け取ります。
正しいアクセス例です。
/sample?age=20
この場合、ageには20が入ります。
では、次のようにアクセスしたらどうなるでしょうか。
/sample?age=abc
abcは数値ではありません。
Spring Bootはint型に変換できないため、400 Bad Requestになります。
つまり、Javaのメソッド本体が本格的に動く前に、リクエストの受け取り段階で失敗しています。
フォーム送信で400が起きる例
HTMLフォームから価格を送る例を考えます。
<form action="/cars/search" method="get">
<input type="number" name="minPrice">
<button type="submit">検索</button>
</form>Controller側です。
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class CarController {
@GetMapping("/cars/search")
public String search(@RequestParam Integer minPrice) {
System.out.println("最低価格: " + minPrice);
return "cars/search";
}
}ブラウザからminPrice=1000000が送られた場合は問題ありません。
しかし、何らかの理由でminPrice=abcが送られると、Integerに変換できず400エラーになることがあります。
新人エンジニアは、「Controllerの引数の型」と「画面から送られる値」が一致しているかを確認してください。
500エラーとは何か
500エラーは、正式にはInternal Server Errorです。
Internal Server Errorは、日本語にすると「サーバー内部エラー」です。
400エラーが「リクエストの形が悪い」のに対して、500エラーは「サーバー側の処理中に例外が起きた」と考えるとわかりやすいです。
たとえば、Javaコードの中でNullPointerExceptionが発生した場合、DB接続に失敗した場合、SQLが間違っていた場合などです。
レストランでたとえるなら、注文用紙は正しいのに、厨房で鍋をひっくり返したり、材料がなかったり、調理手順を間違えたりした状態です。
Spring Bootで500エラーが起きやすい場面
| 原因 | 例 | なぜ500になるか |
|---|---|---|
| NullPointerException | nullの変数に対してメソッドを呼んだ | Java処理中に例外が発生したため |
| SQLエラー | 存在しないカラム名をSELECTした | DB処理中に例外が発生したため |
| DB接続エラー | パスワード違い、DB停止 | サーバー側でDBに接続できないため |
| テンプレートエラー | Thymeleafの変数名間違い | 画面生成中に例外が発生したため |
| 予期しないRuntimeException | throw new RuntimeException() | 例外処理されずに上位へ伝わったため |
500エラーのポイントは、リクエスト自体は受け取れたが、サーバー側の処理中に失敗したという点です。
500エラーのコード例
次のControllerを見てください。
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ErrorSampleController {
@GetMapping("/server-error")
public String serverError() {
String name = null;
return name.toUpperCase();
}
}nameにはnullが入っています。
そのnullに対してtoUpperCase()を呼び出しています。
この場合、NullPointerExceptionが発生します。
結果として、Spring Bootは500 Internal Server Errorを返す可能性があります。
このエラーは、リクエストの書き方が悪いわけではありません。
サーバー側のJavaコードに問題があります。
400と500の違いを一言で表す
400と500の違いを、一言で表すなら次のようになります。
| エラー | 一言で言うと |
|---|---|
| 400 | 送られてきた値や形式がおかしい |
| 500 | 受け取った後のサーバー処理で失敗した |
もう少し実務的に言うと、400は「ユーザー入力、リクエストパラメータ、JSON、URL、型変換」を疑います。
500は「Javaコード、DB、SQL、設定、テンプレート、例外処理」を疑います。
新人エンジニア向けの切り分け手順
エラーが出たら、まずステータスコードを確認します。
| 確認順 | 見るもの | 400の場合 | 500の場合 |
|---|---|---|---|
| 1 | ブラウザやAPIのステータス | 400 Bad Request | 500 Internal Server Error |
| 2 | 送信した値 | 型、必須項目、形式を確認 | 一応確認するが主原因ではないことが多い |
| 3 | Controllerの引数 | @RequestParam、@ModelAttribute、@RequestBodyを確認 | 受け取った後の処理を確認 |
| 4 | コンソールログ | 型変換、バリデーション系の例外を確認 | NullPointerException、SQLExceptionなどを確認 |
| 5 | 修正場所 | HTML、JavaScript、リクエスト形式、Formクラス | Service、DAO、SQL、設定、例外処理 |
エラー調査では、画面だけを見て悩まないでください。
Spring Bootを起動しているコンソールログを見ることがとても大切です。
ブラウザには「400」「500」しか出ていなくても、コンソールには原因のクラス名や行番号が出ていることがあります。
400エラーでよく見る原因
@RequestParamの型が合わない
@GetMapping("/cars")
public String list(@RequestParam int page) {
return "cars/list";
}このControllerに対して、次のようなリクエストを送ると危険です。
/cars?page=abc
pageはint型なので、abcは変換できません。
400エラーになる可能性があります。
必須パラメータが不足している
@GetMapping("/cars/detail")
public String detail(@RequestParam int carId) {
return "cars/detail";
}このControllerではcarIdが必要です。
しかし、次のようにcarIdなしでアクセスした場合です。
/cars/detail
必須パラメータが不足しているため、400エラーになることがあります。
必須ではない値にしたいなら、required=falseを使います。
@GetMapping("/cars/detail")
public String detail(@RequestParam(required = false) Integer carId) {
if (carId == null) {
return "redirect:/cars";
}
return "cars/detail";
}intではなくIntegerにしている点も重要です。
intはnullを持てません。
Integerはnullを持てます。
フォームクラスの数値項目に文字が入る
フォームクラスです。
public class CarSearchForm {
private Integer minPrice;
public Integer getMinPrice() {
return minPrice;
}
public void setMinPrice(Integer minPrice) {
this.minPrice = minPrice;
}
}minPriceにabcのような文字列が送られると、Integerへ変換できず400エラーになる可能性があります。
フォームクラスで数値を受け取る場合は、入力欄のtypeやバリデーションを整えることが大切です。
500エラーでよく見る原因
NullPointerException
CarDto car = carsDao.findById(carId);
String carName = car.getName();carsDao.findById(carId)がnullを返した場合、car.getName()でNullPointerExceptionになります。
この場合は、nullチェックを入れます。
CarDto car = carsDao.findById(carId);
if (car == null) {
return "redirect:/cars";
}
String carName = car.getName();SQLのミス
SELECT car_id, car_name, price FROM cars;
もしcarsテーブルにcar_nameというカラムが存在しなければ、SQLエラーになります。
DAOの中で例外が発生し、適切に処理されなければ500エラーになる可能性があります。
DBエラーが疑われる場合は、次の点を確認します。
| 確認項目 | 内容 |
|---|---|
| テーブル名 | 本当に存在するか |
| カラム名 | スペルミスがないか |
| WHERE条件 | 型やカラム名が正しいか |
| DB接続情報 | URL、ユーザー名、パスワードが正しいか |
| SQLログ | 実際に実行されたSQLは何か |
Thymeleafの変数名ミス
Controllerで次のようにModelへ値を入れたとします。
model.addAttribute("carList", carList);
しかし、HTML側で違う名前を使っていた場合です。
<tr th:each="car : ${cars}">
ControllerではcarList、HTMLではcarsです。
このような変数名のズレで画面生成に失敗し、500エラーになることがあります。
Spring BootでThymeleafを使う場合、Controllerのmodel.addAttributeの名前とHTML側の変数名を必ず合わせましょう。
400と500の責任範囲の違い
新人エンジニアは、「誰が直すべきエラーなのか」という観点でも考えると理解しやすいです。
| エラー | 主に確認する人 | 修正対象 |
|---|---|---|
| 400 | フロントエンド担当、Controller担当、API利用者 | 送信値、フォーム、JavaScript、リクエスト形式 |
| 500 | バックエンド担当、DB担当、インフラ担当 | Javaコード、SQL、DB接続、設定、例外処理 |
ただし、完全に分けられるわけではありません。
たとえば、画面から変な値を送って400になる場合、HTMLやJavaScript側に原因があるかもしれません。
一方で、Controllerの引数設計が厳しすぎて400になっている場合、バックエンド側の設計を見直すこともあります。
エラーコードは、犯人探しの道具ではありません。
原因の場所を絞るための地図です。
API開発での400と500
REST APIを作る場合、400と500の使い分けはとても重要です。
API利用者は、ステータスコードを見て次の対応を判断します。
| ステータス | API利用者の対応 |
|---|---|
| 400 | 送信内容を修正して再リクエストする |
| 500 | 時間を置く、管理者へ連絡する、サーバー側の復旧を待つ |
たとえば、ユーザー登録APIでメールアドレスが空なら400です。
リクエスト内容が悪いからです。
しかし、DBが停止して登録できないなら500です。
サーバー側の問題だからです。
400を返すサンプル
入力値がおかしい場合は、400を明示的に返すことがあります。
package com.example.demo.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserApiController {
@PostMapping("/api/users")
public ResponseEntity<String> createUser(@RequestParam String name) {
if (name == null || name.isBlank()) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body("名前は必須です。");
}
return ResponseEntity.ok("ユーザーを登録しました。");
}
}HttpStatus.BAD_REQUESTは、400 Bad Requestを表します。
SpringのHttpStatusには、BAD_REQUESTやINTERNAL_SERVER_ERRORなどのHTTPステータスが定義されています。
500を返すサンプル
サーバー側で予期しない例外が起きた場合は、500を返すことがあります。
package com.example.demo.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SystemApiController {
@GetMapping("/api/system-error")
public ResponseEntity<String> systemError() {
try {
String value = null;
value.toUpperCase();
return ResponseEntity.ok("成功しました。");
} catch (Exception e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("サーバー内部でエラーが発生しました。");
}
}
}HttpStatus.INTERNAL_SERVER_ERRORは、500 Internal Server Errorを表します。
ただし、実務ではControllerごとにtry-catchを大量に書くより、共通の例外処理を作ることが多いです。
@ControllerAdviceでエラー処理を共通化する
Spring Bootでは、例外処理を共通化するために@ControllerAdviceや@ExceptionHandlerを使うことがあります。
Spring Frameworkのリファレンスでは、@ExceptionHandlerによってController内の例外を処理でき、@ControllerAdviceと組み合わせることで複数Controllerにまたがる例外処理を扱えることが説明されています。
例を見てみましょう。
package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<String> handleBadRequest(Exception e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body("必要なリクエストパラメータが不足しています。");
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleServerError(Exception e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("サーバー内部でエラーが発生しました。");
}
}このようにすると、特定の例外は400、それ以外の予期しない例外は500として返すように整理できます。
ただし、すべてのExceptionを500にまとめるだけでは不十分です。
入力ミス、権限エラー、データなし、競合、サーバーエラーなどは、本来は別のステータスに分けるべき場面もあります。
400と500を間違えると何が困るのか
400と500を正しく返すことは、画面利用者やAPI利用者にとって重要です。
間違えると、対応方法がわからなくなります。
| 間違い | 困ること |
|---|---|
| 入力ミスなのに500を返す | ユーザーはサーバー障害だと思ってしまう |
| サーバー障害なのに400を返す | ユーザーは自分の入力が悪いと思ってしまう |
| 全部500にする | 原因の切り分けが難しくなる |
| 全部200でエラー文だけ返す | API利用側が成功と誤解する |
ステータスコードは、システム同士の会話です。
人間に向けたエラーメッセージだけでなく、ブラウザ、JavaScript、外部API利用者に対して「何が起きたか」を伝える役割があります。
新人エンジニアがログで見るべきポイント
400や500が出たら、まずログを確認します。
| 見るポイント | 意味 |
|---|---|
| 例外クラス名 | どの種類のエラーか |
| メッセージ | 何が原因と書かれているか |
| 行番号 | どのJavaファイルの何行目で起きたか |
| リクエストURL | どの画面やAPIで起きたか |
| 送信値 | どんな値が送られていたか |
400の場合は、次のようなキーワードを探します。
MethodArgumentTypeMismatchException
MissingServletRequestParameterException
MethodArgumentNotValidException
HttpMessageNotReadableException
500の場合は、次のようなキーワードを探します。
NullPointerException
SQLException
DataAccessException
RuntimeException
TemplateInputExceptionエラー名を見るだけで、かなり原因の方向性がわかります。
400エラーを防ぐためのポイント
| 対策 | 内容 |
|---|---|
| HTMLのname属性を確認する | フォームクラスや@RequestParamの名前と合わせる |
| 数値項目はIntegerを使う | 未入力のnullを扱いやすくする |
| 入力チェックを入れる | 空文字、範囲外、形式違いを防ぐ |
| エラーメッセージを表示する | ユーザーが修正しやすくする |
| API仕様を明確にする | 何を送るべきかを決める |
400エラーは、入力やリクエスト形式の問題です。
そのため、画面側とController側の約束をそろえることが大切です。
500エラーを防ぐためのポイント
| 対策 | 内容 |
|---|---|
| nullチェックを行う | NullPointerExceptionを防ぐ |
| SQLを事前に確認する | テーブル名、カラム名、条件を確認する |
| DAOの例外を適切に扱う | DBエラーの原因をログに残す |
| 共通例外処理を作る | 予期しない例外を整理して返す |
| テストを書く | 本番前にバグを見つける |
500エラーは、サーバー側の不具合や想定漏れで起きます。
新人エンジニアは、エラーが出たら「どの行で例外が起きたか」を必ず確認してください。
まとめ
Spring Bootの400エラーと500エラーの違いは、原因の場所で考えると理解しやすいです。
| 比較 | 400 Bad Request | 500 Internal Server Error |
|---|---|---|
| 分類 | クライアントエラー | サーバーエラー |
| 主な原因 | 入力値、リクエスト形式、型変換、必須項目不足 | Java例外、DBエラー、SQLミス、設定ミス |
| よく見る場所 | Controllerに値を渡す前後 | Service、DAO、テンプレート、DB処理 |
| 対応方法 | 送信値やフォームを直す | サーバー側のコードや設定を直す |
| たとえ | 注文用紙の書き方が悪い | 厨房内で調理に失敗した |
一言でまとめるなら、400は「送った内容が悪い」、500は「受け取った後のサーバー処理が悪い」です。
新人エンジニアは、400が出たらリクエストパラメータ、フォーム、型変換、バリデーションを確認してください。
500が出たら、Javaの例外、ログの行番号、SQL、DB接続、Thymeleafの変数名を確認しましょう。
今後の学習では、HTTPステータスコード、@RequestParam、@ModelAttribute、@RequestBody、バリデーション、例外処理、@ControllerAdvice、ログの読み方を順番に学ぶとよいです。まずは、エラーが出たときに「400だから入力側」「500だからサーバー側」と大まかに切り分ける習慣をつけてください!
セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。
投稿者プロフィール

- 代表取締役
-
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。
学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。

