タイトル: Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback.
SEOタイトル: Spring Boot「Whitelabel Error Page」原因と対処|独自エラーページの実装方法
| この記事の要点 |
|
エラー画面
Spring Boot アプリにアクセスして 404 / 500 等のエラーが発生したとき、以下のような無味乾燥なページが表示されます:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat May 15 12:34:56 JST 2024
There was an unexpected error (type=Not Found, status=404).
原因
Spring Boot はデフォルトのフォールバックエラーページとして「Whitelabel Error Page」を表示します。これは:
- 独自の
/errorコントローラが定義されていない - テンプレートエンジンの
error.htmlも無い - 結果として Spring Boot 内蔵の素っ気ない HTML が表示される
本番では UX もブランディングも崩れるので独自エラーページに置き換えます。
対処1: error.html を配置(Thymeleaf)
もっとも簡単。src/main/resources/templates/error.html を作成するだけで Spring Boot が自動でマッピング:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${status} + ' - ' + ${error}">エラー</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="error-container">
<h1 th:text="${status}">500</h1>
<h2 th:text="${error}">Internal Server Error</h2>
<p th:if="${message}" th:text="${message}"></p>
<div th:if="${trace}" class="trace">
<pre th:text="${trace}"></pre>
</div>
<a href="/">トップに戻る</a>
</div>
</body>
</html>
ステータスコードごとに別ファイルを置くと自動で振り分けられます:
src/main/resources/templates/error/
├── 404.html ← 404 専用
├── 500.html ← 500 専用
└── ...
src/main/resources/templates/
└── error.html ← その他すべて
対処2: ErrorController を実装
テンプレートを使わず、コントローラで JSON 等を返す:
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
@RestController
public class CustomErrorController implements ErrorController {
@RequestMapping("/error")
public Map<String, Object> handleError(HttpServletRequest request) {
Integer status = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
String message = (String) request.getAttribute(RequestDispatcher.ERROR_MESSAGE);
String path = (String) request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
Map<String, Object> response = new HashMap<>();
response.put("status", status);
response.put("error", message);
response.put("path", path);
response.put("timestamp", new Date());
return response;
}
}
対処3: @ControllerAdvice で例外を catch
例外型ごとに細かく分岐したい場合(推奨):
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(NotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("NOT_FOUND", ex.getMessage()));
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleForbidden(AccessDeniedException ex) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(new ErrorResponse("FORBIDDEN", ex.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAll(Exception ex) {
log.error("Unexpected error", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("INTERNAL", "サーバエラーが発生しました"));
}
public record ErrorResponse(String code, String message) {}
}
対処4: Whitelabel を無効化
独自のエラーハンドリングだけ使い、Spring の Whitelabel を完全に切る:
# application.properties
server.error.whitelabel.enabled=false
# エラー詳細表示の制御
server.error.include-stacktrace=on_param # never / always / on_param / on_trace_param
server.error.include-message=on_param # never / always / on_param
server.error.include-binding-errors=on_param
server.error.include-exception=false
# /error パスを変更
server.error.path=/api-error
本番 vs 開発でのエラー詳細制御
# application-dev.properties (開発)
server.error.include-stacktrace=always
server.error.include-message=always
server.error.include-exception=true
# application-prod.properties (本番)
server.error.include-stacktrace=never # 機密漏洩防止
server.error.include-message=never
server.error.include-exception=false
404 だけを別ハンドラに
// 404 専用ハンドラ
@ControllerAdvice
public class NotFoundHandler {
@ExceptionHandler(NoHandlerFoundException.class)
public ModelAndView handle404(NoHandlerFoundException ex) {
ModelAndView mav = new ModelAndView("error/404");
mav.setStatus(HttpStatus.NOT_FOUND);
return mav;
}
}
// application.properties で 404 例外を投げるように
// spring.mvc.throw-exception-if-no-handler-found=true
// spring.web.resources.add-mappings=false
JSON / HTML を自動切替
同じ /error でも Accept ヘッダで HTML / JSON を切り替えるパターン:
@Controller
public class CustomErrorController implements ErrorController {
@RequestMapping(value = "/error", produces = MediaType.TEXT_HTML_VALUE)
public String html(HttpServletRequest request, Model model) {
// HTML テンプレートで返す
Integer status = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
model.addAttribute("status", status);
return "error";
}
@RequestMapping(value = "/error")
@ResponseBody
public Map<String, Object> json(HttpServletRequest request) {
// JSON で返す(API リクエスト用)
// ...
return Map.of("status", status, "error", "...");
}
}
関連
- HandlerInterceptor でエラー処理前にロギング・通知を挟める
- Spring Security の AccessDeniedHandler: 403 の独自処理
- RestControllerAdvice: API 専用の @ControllerAdvice
- ProblemDetail (RFC 9457): Spring 6 / Boot 3 で標準化されたエラーレスポンス形式
- Actuator: 健全性チェック / エラー統計の収集