タイトル: 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」原因と対処|独自エラーページの実装方法
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li>Spring Boot の <strong>Whitelabel Error Page</strong></li><li>原因: <strong>独自のエラーページ (/error マッピング)</strong>が定義されていないためのフォールバック表示</li><li>対処1: 独自エラーコントローラ <code>@Controller + /error</code> を実装</li><li>対処2: <code>application.properties</code> で <code>server.error.whitelabel.enabled=false</code></li><li>対処3: <code>src/main/resources/templates/error.html</code> を置く(Thymeleaf 自動マッピング)</li></ul></td></tr></table>
<h2>エラー画面</h2>
<p>Spring Boot アプリにアクセスして 404 / 500 等のエラーが発生したとき、以下のような無味乾燥なページが表示されます:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text">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).</code></pre>
<h2>原因</h2>
<p>Spring Boot は<strong>デフォルトのフォールバック</strong>エラーページとして「Whitelabel Error Page」を表示します。これは:</p>
<ul>
<li>独自の <code>/error</code> コントローラが定義されていない</li>
<li>テンプレートエンジンの <code>error.html</code> も無い</li>
<li>結果として Spring Boot 内蔵の素っ気ない HTML が表示される</li>
</ul>
<p>本番では UX もブランディングも崩れるので独自エラーページに置き換えます。</p>
<h2>対処1: error.html を配置(Thymeleaf)</h2>
<p>もっとも簡単。<code>src/main/resources/templates/error.html</code> を作成するだけで Spring Boot が自動でマッピング:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!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></code></pre>
<p>ステータスコードごとに別ファイルを置くと自動で振り分けられます:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text">src/main/resources/templates/error/
├── 404.html ← 404 専用
├── 500.html ← 500 専用
└── ...
src/main/resources/templates/
└── error.html ← その他すべて</code></pre>
<h2>対処2: ErrorController を実装</h2>
<p>テンプレートを使わず、コントローラで JSON 等を返す:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-java">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;
}
}</code></pre>
<h2>対処3: @ControllerAdvice で例外を catch</h2>
<p>例外型ごとに細かく分岐したい場合(推奨):</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-java">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) {}
}</code></pre>
<h2>対処4: Whitelabel を無効化</h2>
<p>独自のエラーハンドリングだけ使い、Spring の Whitelabel を完全に切る:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-properties"># 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</code></pre>
<h2>本番 vs 開発でのエラー詳細制御</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-properties"># 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</code></pre>
<h2>404 だけを別ハンドラに</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-java">// 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</code></pre>
<h2>JSON / HTML を自動切替</h2>
<p>同じ <code>/error</code> でも Accept ヘッダで HTML / JSON を切り替えるパターン:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-java">@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", "...");
}
}</code></pre>
<h2>関連</h2>
<ul>
<li><strong>HandlerInterceptor</strong> でエラー処理前にロギング・通知を挟める</li>
<li><strong>Spring Security の AccessDeniedHandler</strong>: 403 の独自処理</li>
<li><strong>RestControllerAdvice</strong>: API 専用の @ControllerAdvice</li>
<li><strong>ProblemDetail (RFC 9457)</strong>: Spring 6 / Boot 3 で標準化されたエラーレスポンス形式</li>
<li><strong>Actuator</strong>: 健全性チェック / エラー統計の収集</li>
</ul>