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=abcintやIntegerに変換できないため
必須パラメータがないnameが必要なのに送っていない@RequestParamの必須値が不足しているため
バリデーションエラー必須入力が空、文字数オーバー@Validや入力チェックに失敗したため
JSONの形式が壊れている{ "name": "田中", }JSONとして読み取れないため
日付形式が合わない2026/99/99LocalDateなどに変換できないため

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になるか
NullPointerExceptionnullの変数に対してメソッドを呼んだJava処理中に例外が発生したため
SQLエラー存在しないカラム名をSELECTしたDB処理中に例外が発生したため
DB接続エラーパスワード違い、DB停止サーバー側でDBに接続できないため
テンプレートエラーThymeleafの変数名間違い画面生成中に例外が発生したため
予期しないRuntimeExceptionthrow 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 Request500 Internal Server Error
2送信した値型、必須項目、形式を確認一応確認するが主原因ではないことが多い
3Controllerの引数@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 Request500 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年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。