
Tomcat 11 で HTTP/1.1 リクエストヘッダーが小文字化されなくなった話
こんにちは。小売流通ソリューション部の宮部です。
Spring Boot のメジャーバージョンアップ作業を行っていたところTomcat 11の特定バージョンからHTTP/1.1リクエストヘッダー名の小文字化が行われなくなっていることに気づきました。
changelogを見ると同じ変更がTomcat 9/10にもバックポートされていましたが、影響を受けるのはTomcat 11だけという少しややこしい事情があったので整理してみます。
気づいた経緯
Spring Bootのメジャーバージョンアップに伴いTomcat 11に切り替わったあとで2つのテストが失敗しました。
アクセスログのフォーマットが合致しない
ログに記録されるリクエストヘッダー名が小文字前提のアサーションになっており、実際の値と一致しなくなりました。
Cookieヘッダーの値をマスクできない
ヘッダー名を"cookie"と比較してマスク対象を判定していたところ、テストの結果ではマスクされていませんでした。
何が変わったのか
Tomcat 11.0.12 (2025-10-07リリース) で、以下の変更が入りました。
Store HTTP request headers using the original case for the header name rather than forcing it to lower case.
Apache Tomcat 11 ChangelogのCoyoteセクションに記載されています。
これまでTomcatはHTTP/1.1リクエストのヘッダー名を小文字に変換していました。
例えばクライアントがContent-Typeで送ったヘッダーはcontent-typeとして保持されていました。
この変更によりクライアントが送ったそのままのケースで保持されるようになります。
実際の変更内容
変更の実体はHttpHeaderParser.javaから数行削除しただけです。
// 削除されたコード
private static final byte A = (byte) 'A';
private static final byte a = (byte) 'a';
private static final byte Z = (byte) 'Z';
private static final byte LC_OFFSET = A - a;
// ...
// chr is next byte of header name. Convert to lowercase.
if (chr >= A && chr <= Z) {
source.getHeaderByteBuffer().put(pos, (byte) (chr - LC_OFFSET));
}
A〜Zの大文字を検出して小文字に変換する処理が消えています。
コミット(11.0.x): e70eb689 - Store request header names using original case rather than forcing to lc
もともとの目的はtrailerヘッダー
この変更の本来の目的はHTTP trailerヘッダーの小文字強制をやめることでした。
trailerヘッダーとは、chunked transfer encodingでボディの後に送られるヘッダーのことです。
変更はTomcat 9/10/11の3つのバージョンに同時にコミットされています。
| ブランチ | コミット | 最初のリリースバージョン |
|---|---|---|
| 9.0.x | aa23770 |
9.0.110 |
| 10.1.x | bbb54e8 |
10.1.47 |
| 11.0.x | e70eb68 |
11.0.12 |
Tomcat 11だけ全リクエストヘッダーに影響した理由
同じHttpHeaderParser.javaへの変更が3つのバージョンに入っていますが影響範囲が異なります。
原因はHttpHeaderParserの呼び出し元がバージョンによって違うからでした。
Tomcat 9 / 10の呼び出し元
| 呼び出し元 | 用途 |
|---|---|
ChunkedInputFilter |
trailerヘッダーのパース |
Tomcat 11の呼び出し元
| 呼び出し元 | 用途 |
|---|---|
ChunkedInputFilter |
trailerヘッダーのパース |
Http11InputBuffer |
リクエストヘッダーのパース |
Tomcat 11ではHTTP/1.1リクエストヘッダーのパース処理がHttpHeaderParserを使うようにリファクタリングされています。
e5acf2cf - Refactor HTTP header parsing to use common parser, 11.0.0-M20以降
そのため、trailerヘッダー向けの変更がすべてのリクエストヘッダーに波及しています。
Tomcat 9/10ではHttp11InputBufferがHttpHeaderParserを使っておらず、内部で小文字化の処理を行っているため通常のリクエストヘッダーには影響がありません。
trailerヘッダーの小文字化だけが廃止されたという意図どおりの変更です。
Tomcat 9/10のchangelog修正
この影響範囲の違いからTomcat 9/10のchangelogが修正されています。
2026年4月14日現在ではchangelogのページには反映されていませんが、コミットはされています。
元のchangelog:
Store HTTP request headers using the original case for the header name rather than forcing it to lower case.
修正後のchangelog:
Store HTTP trailer headers using the original case for the header name rather than forcing it to lower case.
「request headers」を「trailer headers」に書き換えられています。
Tomcat 9/10では実際に影響するのはtrailerヘッダーだけなので、changelogの記述を実態に合わせた形です。
| ブランチ | changelog修正コミット |
|---|---|
| 9.0.x | 1441f0a |
| 10.1.x | 10a1cef |
もちろんTomcat 11のchangelogは修正されていません。
Tomcat 11では実際に全リクエストヘッダーが影響を受けるため、元の記述のままで正しいからです。
breaking changeではないのか
changelogを見ていると他のbug fixと同じラベルがつけられているのがわかりますが、それにしては大きな変更に思えます。
RFC 7230 Section 3.2には以下のように記載されています。
Each header field consists of a case-insensitive field name
HTTPヘッダー名はcase-insensitiveと定義されているため、小文字化をやめたこと自体は仕様違反ではありません。HttpServletRequest#getHeader()もcase-insensitiveで検索するため、通常のアプリケーションコードには影響しません。
つまり、これはbreaking changeではありません。
とはいえ実運用面では注意が必要
RFC的には問題ないですが、影響が出る可能性はあります。
アクセスログの検索
これまでのアクセスログではヘッダー名がすべて小文字で記録されていたかもしれません。
Tomcat 11.0.12以降ではContent-Typeのように元のケースで記録されるため、ログ検索のクエリを見直す必要があるかもしれません。
# 以前: すべて小文字
content-type: application/json
# 11.0.12以降: クライアントが送ったケースそのまま
# 大文字と小文字どちらもあり得る
Content-Type: application/json
content-type: application/json
ヘッダー名を直接比較しているコード
getHeader()はcase-insensitiveですが、ヘッダー名を文字列として直接比較しているコードがあれば影響を受けます。
// これは問題ない(case-insensitive)
request.getHeader("cookie");
// これは影響を受ける可能性がある
Collections.list(request.getHeaderNames()).forEach(name -> {
if (name.equals("cookie")) {
log.info("{}: [MASKED]", name);
} else {
log.info("{}: {}", name, request.getHeader(name));
}
});
まとめ
同じコード変更が3つのバージョンにバックポートされたにもかかわらず、内部アーキテクチャのリファクタリング状況の違いで影響範囲が異なるという、なかなか気づきにくい変更でした。
ヘッダー名を小文字前提で直接比較しているコードやログ検索クエリなど、場合によっては処理に影響がある可能性もあるためご注意ください。









