0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SpringBoot × axios 例外処理の実装方法

Last updated at Posted at 2025-09-21

SpringBoot × axios の例外処理の実装方法をまとめます。

通常SpringBootで例外が発生した際、SpringBootの共通で設定したエラー画面に遷移する仕様なのですが、axiosの場合は、javascriptでレスポンスを受け取るので、エラー内容を表示するには個別の制御が必要になります。
Controllerの戻り値について以下の2パターンで実装例を紹介します。
Map<String, Object>
ResponseEntity<Map<String, Object>>

Controller設計方針① 戻り値 Map<String, Object>

  • try-catchを記載する
    業務処理中の例外をキャッチして、整形済みのJSONレスポンスを返すために使用します。

  • throws の記載は不要
    throws は呼び出し元に例外を伝播するためのものですが、Controllerは最上位のエンドポイントです。
    ここで throws を付けると、Springが例外をキャッチできず、意図しないレスポンス(HTMLエラー画面など)が返る可能性があります。

  • 入力チェック(バリデーション)は try-catch とは別ブロックで記載する
    バリデーションは業務例外とは異なるため、bindingResult.hasErrors() で分岐し、早期リターンするのが望ましいです。

  • 戻り値を Map<String, Object> にする
    フロント(JavaScript)側では、すべてのレスポンスを HTTP 200 として受け取ることになります。
    successbindErrors のキーを見て、処理を分岐させる設計になります。

サンプルコード 戻り値 Map<String, Object>

@PostMapping(regist)
@ResponseBody
public Map<String, Object> regist(@ModelAttribute @Valid TestForm form, BindingResult bindingResult) {
// 入力チェック
if (bindingResult.hasErrors()) {
  Map<String, Object> bindErrors = bindingResult.getModel();
  return Map.of("success", false, "bindErrors", bindErrors);
}

try {
  Dto dto = toDto(form); // 変換処理メソッドは割愛します。
  registService.regist(form); // 登録処理メソッドは割愛します。
  return Map.of("success", true);
} catch (Exception e) {
  return Map.of(
    "success", false,
    "message", "予期せぬエラーが発生しました",
    "errorCode", "E999"
  );
}

💡 補足:レスポンスの構造

  • 成功:HTTPステータス:200success: true
  • 入力チェックエラー:HTTPステータス:200success: falsebindErrors
  • その他例外:HTTPステータス:200success: falsemessageerrorCode

javascript設計方針① 戻り値 Map<String, Object>

  • thenブロック内に記載する
     サーバーからの戻り値が Map<String, Object>だった場合、すべてHTTPステータス200として受け取ることになります。よってHTTPステータスで制御できないので、successの値やbindErrorsの有無で条件分岐させます。

javascript

axios.post('/regist', formData)
  .then(response => {
    const data = response.data;

    if (data.success) {
      // 正常処理
      alert('登録が完了しました');
    } else if (data.bindErrors) {
      // 入力チェックエラー
      console.log('バリデーションエラー:', data.bindErrors);
      showValidationErrors(data.bindErrors); // エラー表示メソッドは割愛します。
    } else {
      // その他の例外
      console.error('サーバー例外:', data.errorCode, data.message);
      alert(data.message || '予期せぬエラーが発生しました');
    }
  })
  .catch(error => {
    console.error('通信エラー:', error);
    alert('通信に失敗しました。時間をおいて再度お試しください。');
  });

Controller設計方針② 戻り値 ResponseEntity<Map<String, Object>>

  • 戻り値を ResponseEntity<Map<String, Object>> にする場合
    サーバー側での例外状況に応じて、HTTPステータスコードを柔軟に変更できます。
    JavaScript側では response.status を使って、ステータスコードによる制御も可能になります。
    ResponseEntityのメソッドによって、HTTPステータスを変更することが可能です。

ResponseEntity の主なメソッド一覧

メソッド HTTPステータス 用途
ResponseEntity.ok(body) 200 OK 正常レスポンス
ResponseEntity.badRequest().body(body) 400 Bad Request バリデーションエラーなど
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body) 500 Internal Server Error サーバーエラー

サンプルコード 戻り値 ResponseEntity<Map<String, Object>>

@PostMapping(regist)
@ResponseBody
public ResponseEntity<Map<String, Object>> regist(@ModelAttribute @Valid TestForm form, BindingResult bindingResult) {

  if (bindingResult.hasErrors()) {
    Map<String, Object> bindErrors = bindingResult.getModel();
    return ResponseEntity.ok(Map.of("success", false, "bindErrors", bindErrors));
  }

  try {
    Dto dto = toDto(form); // 変換処理メソッドは割愛します。
    registService.regist(dto); // 登録処理メソッドは割愛します。
    return ResponseEntity.ok(Map.of("success", true));
  } catch (Exception e) {
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
      Map.of(
        "success", false,
        "message", "予期せぬエラーが発生しました",
        "errorCode", "E999"
      )
    );
  }
}

💡 補足:レスポンスの構造(HTTPステータスを活用)

  • 正常 :HTTPステータス:200 OK{"success": true}
  • 入力チェックエラー: HTTPステータス:200 OK{"success": false, "bindErrors": {...}}
  • その他の例外:HTTPステータス:500 Internal Server Error{"success": false, "message": "予期せぬエラーが発生しました", "errorCode": "E999"}

フロント側ではHTTPステータスによって、ブロックを分けて記述する必要があります。200~300台がthenブロックでそれ以外がcatchブロックです。

axiosの挙動とHTTPステータスの関係

HTTPステータス 説明 axiosの挙動 入るブロック
200〜299 正常レスポンス 成功とみなす .then()
300〜399 リダイレクト 成功とみなす(※) .then()
400〜499 クライアントエラー(Bad Requestなど) エラーとみなす .catch()
500〜599 サーバーエラー(Internal Server Errorなど) エラーとみなす .catch()
ネットワークエラー 通信失敗・タイムアウトなど エラーとみなす .catch()

※ リダイレクトはブラウザや設定によって挙動が異なる場合があります。

javascript

axios.post('/regist', formData)
  .then(response => {
    const data = response.data;

    if (data.success) {
      alert('登録が完了しました');
    } else if (data.bindErrors) {
      showValidationErrors(data.bindErrors);
    } else {
      alert(data.message || '予期せぬエラーが発生しました');
    }
  })
  .catch(error => {
    // ここで500などのHTTPエラーを処理
    if (error.response && error.response.status === 500) {
      const data = error.response.data;
      console.error('サーバー例外:', data.errorCode, data.message);
      alert(data.message || 'サーバーでエラーが発生しました(500)');
    } else {
      console.error('通信エラー:', error);
      alert('通信に失敗しました。時間をおいて再度お試しください。');
    }
  });

Service設計方針

Service層では、意図的に例外処理を行いたいケースを除き、try-catchthrows といった例外処理を明示的に記述する必要はありません。
なぜなら、Service層で発生した例外は、呼び出し元である Controller 層で制御する設計とするためです。
(※ただし、Controller側で例外を網羅的に try-catch していることが前提です)

また、IOException のようにチェック例外(checked exception)を throws で明示しなければならないメソッドがある場合でも、
EclipseなどのIDEを使用していれば、記載漏れがあるとコンパイルエラーとして警告してくれます。
そのため、警告が表示された箇所にのみ throws を付与すれば十分です。

このように、Service層では例外処理の責務を持たせず、Controller層で一元的に例外をハンドリングすることで、 コードの見通しや保守性を高めることができます。

Service層サンプルコード

// Service層では、try-catchもthrowsも基本的に実装不要。
public void insert(Dto dto) {
  registMapper.insert(dto);  
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?