Spring Boot 3 へ移行した後、一部の画面だけ入力チェックが効かなくなりました。必須項目を空にして送っても、エラーにならずに登録処理まで進みます。
エラーメッセージが一切出ない のがこの問題の厄介なところです。
結論:入力フォームのクラスに import javax.validation.constraints.NotBlank; が残っていました。 Spring Boot 3 の入力チェックは jakarta.validation のアノテーションしか見ません。通常は javax の import が残っているとコンパイルエラーになりますが、依存しているライブラリが古い javax.validation を持ち込んでいると、コンパイルが通ってしまいます。
躓いたポイント
環境
| 項目 | バージョン |
|---|---|
| OS | Windows 11 |
| JDK | Eclipse Temurin 21.0.12 |
| Spring Boot | 3.5.16(Spring Framework 6.2.19、Hibernate Validator 8.0.3) 4.1.1(Spring Framework 7.0.9、Hibernate Validator 9.1.3) |
| Maven | 3.9.16 |
起きたこと
Spring Boot 2.7 から 3 への移行で、javax. で始まる import を jakarta. に一括置換しました。そのとき、一部のフォームクラスが置換から漏れていました。
// Spring Boot 2 の頃のまま残った import
import javax.validation.constraints.NotBlank;
public class JavaxUserForm {
@NotBlank
private String name;
// getter / setter
}
コントローラーは jakarta.validation.Valid に直してあります。
@PostMapping("/javax/users")
public String createJavax(@Valid @RequestBody JavaxUserForm form) {
return "registered: [" + form.getName() + "]";
}
古いライブラリが javax.validation:validation-api を推移的に持ち込んでいたため、javax の import のままでもコンパイルが通りました(検証では、この状態を validation-api 2.0.1.Final を直接指定して再現しています)。
name を空にして送ると、400 ではなく 200 が返り、登録処理が動きます。
Status = 200
Body = registered: []
import だけを jakarta.validation.constraints.NotBlank に変えた同じフォームでは、期待どおり 400 になります。
WARN ... Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.example.demo.UserController.createJakarta(com.example.demo.JakartaUserForm): [Field error in object 'jakartaUserForm' on field 'name': rejected value []; ...
Status = 400
Spring Boot 3.5.16 と 4.1.1 のどちらでも同じ結果でした。
どうやって解決したか
1. 残っている javax の import を探す
まずソース全体から javax の import を探します。Git で管理していれば次のコマンドで一覧できます。
git grep -n -E "import javax\.(validation|persistence|servlet|annotation|transaction)"
javax.sql、javax.crypto、javax.transaction.xa、javax.annotation.processing など、Java 本体に含まれるパッケージは Jakarta EE の移行対象ではないので、そのままで構いません(上のコマンドでは javax.transaction.xa と javax.annotation.processing も表示されるので、見分けてください)。
2. javax.validation を持ち込んでいる依存を特定する
mvn dependency:tree -Dincludes=javax.validation,jakarta.validation
[INFO] com.example:validation:jar:0.0.1
[INFO] +- org.springframework.boot:spring-boot-starter-validation:jar:3.5.16:compile
[INFO] | \- org.hibernate.validator:hibernate-validator:jar:8.0.3.Final:compile
[INFO] | \- jakarta.validation:jakarta.validation-api:jar:3.0.2:compile
[INFO] \- javax.validation:validation-api:jar:2.0.1.Final:compile
jakarta.validation-api と javax.validation の validation-api が同時に入っています。実際のプロジェクトでは、validation-api が何かのライブラリの下にぶら下がって表示されます。
3. javax.validation を依存から外し、import を jakarta に直す
持ち込んでいるライブラリの依存から javax.validation を除外します。
<dependency>
<groupId>(持ち込んでいるライブラリ)</groupId>
<artifactId>(持ち込んでいるライブラリ)</artifactId>
<exclusions>
<exclusion>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
除外すると、置換漏れの箇所がコンパイルエラーとして表に出ます。
[ERROR] .../JavaxUserForm.java:[4,36] パッケージjavax.validation.constraintsは存在しません
[ERROR] .../JavaxUserForm.java:[7,6] シンボルを見つけられません
エラーになった箇所の import を jakarta.validation に直せば、入力チェックが効くようになります。
ライブラリが実行時に javax.validation のクラスを使っている場合、除外するとそのライブラリが動かなくなることがあります。除外した後はテストを流して確かめ、動かない場合はライブラリ自体を Jakarta EE 対応版に上げます。
解決できた根拠
Spring Boot 3 は jakarta パッケージを使い、古い Java EE の依存が残らないよう注意が必要
Spring Boot 3.0 の移行ガイドの「Jakarta EE」の節に、次のように書かれています。
You need to be especially careful that older Java EE dependencies are no longer directly or transitively used in your build.
(古い Java EE の依存が、直接にも推移的にもビルドで使われていないことに特に注意してください)
Jakarta EE now uses jakarta packages rather than javax.
Bean Validation 3.0 でパッケージが jakarta に変わった
Jakarta Bean Validation 3.0 の変更点は「Moved to the jakarta.* namespace」です(Jakarta Bean Validation 3.0)。Spring Boot 3 が使う Hibernate Validator 8.0 は、この Jakarta Bean Validation の参照実装です(Hibernate Validator 8.0 Reference Guide)。
javax.validation.constraints.NotBlank と jakarta.validation.constraints.NotBlank は、名前が似ているだけで Java としては別のアノテーションです。Hibernate Validator 8.0 以降は jakarta の方しか制約として扱わないため、javax の @NotBlank は付いていないのと同じになります。
@RequestBody のチェックは jakarta.validation.Valid が対象で、違反すると MethodArgumentNotValidException
Spring Framework のリファレンスに、@RequestBody などの引数に @jakarta.validation.Valid を付けるとチェックされ、違反時は MethodArgumentNotValidException になると書かれています。
MethodArgumentNotValidException is the exception raised when validating a method parameter individually.
jakarta 版のフォームでは、ログにこの例外が出て 400 になりました。javax 版では例外が出ていません。