1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

エラーハンドリング — Spring Boot側でエラーを返してNext.jsで表示する

1
Posted at

エラーハンドリング — Spring Boot側でエラーを返してNext.jsで表示する

はじめに

APIを作ると必ず必要になるのがエラーハンドリングです。Spring Boot側で適切なエラーレスポンスを返し、Next.js側でそれをユーザーにわかりやすく表示するまでの流れを整理します。


Spring Boot側:エラーレスポンスの統一

バラバラなエラー形式はフロントエンドが扱いにくいため、共通のエラーレスポンス構造を定義します。

// エラーレスポンスDTO
public class ErrorResponse {
    private int status;
    private String message;
    private LocalDateTime timestamp;

    public ErrorResponse(int status, String message) {
        this.status = status;
        this.message = message;
        this.timestamp = LocalDateTime.now();
    }

    // getter省略
}

カスタム例外クラス

HTTPステータスに対応した例外クラスを定義します。

// 404用
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

// 400用
public class BadRequestException extends RuntimeException {
    public BadRequestException(String message) {
        super(message);
    }
}

@RestControllerAdvice — 例外をまとめてハンドリング

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 404: リソースが見つからない
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(404, ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    // 400: リクエストが不正
    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ErrorResponse> handleBadRequest(BadRequestException ex) {
        ErrorResponse error = new ErrorResponse(400, ex.getMessage());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }

    // 401: 認証エラー
    @ExceptionHandler(UnauthorizedException.class)
    public ResponseEntity<ErrorResponse> handleUnauthorized(UnauthorizedException ex) {
        ErrorResponse error = new ErrorResponse(401, ex.getMessage());
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
    }

    // 500: 予期せぬエラー(全体のフォールバック)
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
        ErrorResponse error = new ErrorResponse(500, "サーバーエラーが発生しました");
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

Service層での例外スロー

@Service
public class UserService {

    public UserResponse findById(Long id) {
        return userRepository.findById(id)
                .map(UserResponse::new)
                .orElseThrow(() -> new ResourceNotFoundException(
                    "ユーザーが見つかりません: id=" + id
                ));
    }
}

エラーレスポンスのJSON例:

{
  "status": 404,
  "message": "ユーザーが見つかりません: id=99",
  "timestamp": "2026-07-30T10:00:00"
}

Next.js側:エラーを受け取って表示

Server Componentでのエラーハンドリング

// app/users/[id]/page.tsx
export default async function UserDetailPage({
  params,
}: {
  params: { id: string };
}) {
  const res = await fetch(
    `${process.env.API_URL}/api/users/${params.id}`
  );

  if (!res.ok) {
    const error = await res.json();

    if (res.status === 404) {
      notFound(); // Next.jsのnot-found.tsxを表示
    }

    throw new Error(error.message); // error.tsxにフォールバック
  }

  const user = await res.json();
  return <div>{user.name}</div>;
}

not-found.tsx — 404専用ページ

// app/users/[id]/not-found.tsx
export default function NotFound() {
  return (
    <div>
      <h2>ユーザーが見つかりません</h2>
      <a href="/users">一覧に戻る</a>
    </div>
  );
}

error.tsx — 予期せぬエラーのフォールバック

// app/error.tsx
"use client";

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>エラーが発生しました</h2>
      <p>{error.message}</p>
      <button onClick={reset}>再試行</button>
    </div>
  );
}

Server Actionsでのエラーハンドリング

// useActionStateでエラーメッセージをフォームに返す
"use server";

export async function createUser(
  prevState: { error: string | null },
  formData: FormData
) {
  const res = await fetch(`${process.env.API_URL}/api/users`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      name: formData.get("name"),
      email: formData.get("email"),
    }),
  });

  if (!res.ok) {
    const error = await res.json();
    return { error: error.message }; // フォームにエラーを返す
  }

  redirect("/users");
}

まとめ

レイヤー 実装 役割
Spring Boot カスタム例外クラス HTTPステータスに対応した例外を定義
Spring Boot @RestControllerAdvice 例外をまとめてキャッチしてJSON返却
Next.js notFound() 404をnot-found.tsxに転送
Next.js error.tsx 予期せぬエラーのUI表示
Next.js Server Actions フォームエラーをstateで返す

次回は DBスキーマ設計とJPA — Entity・DTO・Repository・Serviceの関係を整理します。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?