Bean Validation (Jakarta Bean Validation)
バリデーションライブラリ
Bean Validation (Jakarta Bean Validation)
概要
Jakarta Bean Validation(旧Java Bean Validation)は、Javaアプリケーション向けの標準的なバリデーションフレームワークです。アノテーションベースのアプローチで、Beanのプロパティが特定の基準を満たすことを保証します。Jakarta EEおよびJava SEの一部として、エンタープライズアプリケーションやAPIバリデーションに広く使用されています。
詳細
Jakarta Bean Validationは、オブジェクトレベルの制約宣言とバリデーション機能を提供します。@NotNull、@Min、@Maxなどのアノテーションを使用して、宣言的にバリデーションルールを定義できます。バージョン3.0からはjakartaパッケージ名前空間に移行し、3.1では最小Java要件がJava 17に更新されました。
主要な特徴:
- 標準バリデーションアノテーション: @NotNull、@Size、@Min、@Max、@Email、@Pattern等の豊富な組み込み制約
- カスタム制約: 独自のバリデーションロジックを実装可能
- グループバリデーション: 部分的なバリデーションや順序制御が可能
- メソッドバリデーション: パラメータと戻り値のバリデーション(1.1以降)
- カスケーディング: @Validによる関連オブジェクトの再帰的バリデーション
- コンテナ要素バリデーション: List<@Valid Employee>のような型引数のバリデーション(2.0以降)
Hibernate Validatorが参照実装として提供され、Spring BootやJakarta EEコンテナとシームレスに統合されます。
メリット・デメリット
メリット
- 標準化: Java EE/Jakarta EEの標準仕様として業界標準
- 宣言的アプローチ: アノテーションによる簡潔で読みやすいコード
- 統合性: Spring Boot、JAX-RS、JPAなど主要フレームワークとの優れた統合
- 拡張性: カスタムバリデータの作成が容易
- 国際化対応: エラーメッセージの多言語サポート
- パフォーマンス: 効率的なバリデーション実行
デメリット
- 学習曲線: グループバリデーションなど高度な機能の理解に時間が必要
- アノテーション依存: 過度の使用によりコードが冗長になる可能性
- 複雑なバリデーション: ビジネスロジックが複雑な場合、カスタムバリデータが増える
- バージョン移行: javax→jakarta名前空間への移行作業が必要(Spring Boot 3以降)
参考ページ
- Jakarta Validation 公式サイト
- Jakarta Bean Validation 仕様書
- Hibernate Validator リファレンスガイド
- Spring Boot バリデーション
- Jakarta EE チュートリアル
書き方の例
基本的なバリデーション
import jakarta.validation.constraints.*;
public class User {
@NotNull(message = "名前は必須です")
@Size(min = 2, max = 50, message = "名前は2文字以上50文字以下で入力してください")
private String name;
@NotNull(message = "メールアドレスは必須です")
@Email(message = "有効なメールアドレスを入力してください")
private String email;
@Min(value = 18, message = "年齢は18歳以上である必要があります")
@Max(value = 120, message = "年齢は120歳以下である必要があります")
private int age;
@Pattern(regexp = "^\\d{3}-\\d{4}$",
message = "郵便番号は XXX-XXXX 形式で入力してください")
private String postalCode;
// getters and setters
}
カスタムバリデータの作成
// カスタムアノテーション
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
@Documented
public @interface PhoneNumber {
String message() default "無効な電話番号です";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String countryCode() default "JP";
}
// バリデータ実装
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
private String countryCode;
@Override
public void initialize(PhoneNumber annotation) {
this.countryCode = annotation.countryCode();
}
@Override
public boolean isValid(String phoneNumber, ConstraintValidatorContext context) {
if (phoneNumber == null) {
return true; // @NotNullと組み合わせて使用
}
// 日本の電話番号形式をチェック
if ("JP".equals(countryCode)) {
return phoneNumber.matches("^0\\d{1,4}-\\d{1,4}-\\d{4}$") ||
phoneNumber.matches("^0\\d{9,10}$");
}
return false;
}
}
グループバリデーション
// バリデーショングループの定義
public interface BasicInfo {}
public interface AdvancedInfo {}
public class Employee {
@NotNull(groups = BasicInfo.class)
private String name;
@NotNull(groups = BasicInfo.class)
@Email(groups = BasicInfo.class)
private String email;
@NotNull(groups = AdvancedInfo.class)
@Size(min = 10, max = 10, groups = AdvancedInfo.class)
private String employeeId;
@Min(value = 0, groups = AdvancedInfo.class)
@Max(value = 1000000, groups = AdvancedInfo.class)
private BigDecimal salary;
}
// 使用例
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
// 基本情報のみバリデーション
Set<ConstraintViolation<Employee>> basicViolations =
validator.validate(employee, BasicInfo.class);
// 詳細情報のみバリデーション
Set<ConstraintViolation<Employee>> advancedViolations =
validator.validate(employee, AdvancedInfo.class);
// すべてバリデーション
Set<ConstraintViolation<Employee>> allViolations =
validator.validate(employee, BasicInfo.class, AdvancedInfo.class);
カスケードバリデーション
public class Order {
@NotNull
private String orderId;
@NotNull
@Valid // カスケードバリデーションを有効化
private Customer customer;
@NotEmpty
@Valid // リスト要素もバリデーション
private List<@Valid OrderItem> items;
@Valid
private Map<@NotNull String, @Valid Product> productMap;
}
public class Customer {
@NotNull
@Size(min = 2, max = 100)
private String name;
@NotNull
@Valid
private Address address;
}
public class Address {
@NotBlank
private String street;
@NotBlank
@Pattern(regexp = "\\d{3}-\\d{4}")
private String postalCode;
}
Spring Boot統合
// REST Controller での使用
@RestController
@RequestMapping("/api/users")
@Validated // クラスレベルでバリデーション有効化
public class UserController {
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
// @Validにより自動的にバリデーションが実行される
// バリデーションエラーの場合、400 Bad Requestが返される
return ResponseEntity.ok(userService.save(user));
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@Valid @RequestBody User user) {
return ResponseEntity.ok(userService.update(id, user));
}
}
// エラーハンドリング
@RestControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
}
メソッドバリデーション
@Service
@Validated // メソッドバリデーションを有効化
public class PaymentService {
public void processPayment(
@NotNull @Valid CreditCard card,
@Min(1) @Max(1000000) BigDecimal amount) {
// メソッドパラメータが自動的にバリデーションされる
}
@NotNull
@Valid
public Receipt completeTransaction(
@NotNull String transactionId) {
// 戻り値もバリデーション可能
return new Receipt();
}
}
// 設定(Spring Boot では自動設定)
@Configuration
@EnableMethodValidation
public class ValidationConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}