Hibernate Validator
ライブラリ
Hibernate Validator
概要
Hibernate ValidatorはJava Bean Validation(JSR 380)の参照実装であり、Javaアプリケーションにおけるバリデーションのデファクトスタンダードです。アノテーションベースの宣言的バリデーションを提供し、Spring Boot、Jakarta EE、Hibernateなどの主要フレームワークで標準採用されています。2025年現在、Jakarta Bean Validation 3.1.1とJakarta EE 11に対応し、最新版9.0系ではJava 17以上を要求する最新のJavaエコシステムに完全対応しています。
詳細
Hibernate Validator 9.0は2025年最新のリリースで、Jakarta EE 11の参照実装として位置づけられています。Bean Validation 2.0(JSR 380)の仕様に加え、Jakarta Validation 3.1.1の新機能を実装し、コンテナ要素のバリデーション、型引数への制約適用、新しい組み込み制約などの強力な機能を提供します。2025年版では韓国住民登録番号(@KorRRN)やビットコインアドレス(@BitcoinAddress)など、現代的なバリデーション需要に対応した新制約を追加し、JPMS(Java Platform Module System)サポートも強化されています。
主な特徴
- 宣言的バリデーション: アノテーションによる明確で読みやすいバリデーション定義
- Jakarta EE統合: Jakarta EE 11完全対応と企業向けアプリケーション開発支援
- 豊富な組み込み制約: @NotNull、@Size、@Email等50以上のバリデーション制約
- カスタムバリデーター: 独自のビジネスルールに対応する柔軟な拡張機能
- 国際化対応: メッセージの多言語化と地域特化バリデーション
- コンテナバリデーション: List<@Valid User>等のジェネリック型要素バリデーション
メリット・デメリット
メリット
- Java企業アプリケーション開発の業界標準
- Spring Boot、Jakarta EEでの標準採用による高い互換性
- 宣言的記述による保守性とコードの可読性向上
- 豊富なドキュメントとコミュニティサポート
- パフォーマンスが最適化された成熟したライブラリ
- メッセージの国際化機能による多言語対応
デメリット
- Java専用でクロスプラットフォーム対応なし
- 複雑なバリデーションでは設定が複雑になる
- 学習コストが比較的高い(Bean Validation仕様の理解が必要)
- 依存関係が重い(Jakarta EE環境が前提)
- 動的バリデーションルールの実装が困難
- パフォーマンス要求が極めて高い場合は他選択肢を検討
参考ページ
書き方の例
インストールと基本セットアップ
<!-- Maven dependency -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>9.0.1.Final</version>
</dependency>
<!-- Expression Language実装(必要に応じて) -->
<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
<version>6.0.0</version>
</dependency>
<!-- CDI環境での統合(任意) -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-cdi</artifactId>
<version>9.0.1.Final</version>
</dependency>
// Gradle dependency
dependencies {
implementation 'org.hibernate.validator:hibernate-validator:9.0.1.Final'
implementation 'org.glassfish.expressly:expressly:6.0.0'
}
基本的なエンティティバリデーション
import jakarta.validation.constraints.*;
import jakarta.validation.Valid;
import java.time.LocalDate;
import java.util.List;
public class User {
@NotNull(message = "IDは必須です")
private Long id;
@NotBlank(message = "名前は必須です")
@Size(min = 2, max = 50, message = "名前は2文字以上50文字以下で入力してください")
private String name;
@Email(message = "有効なメールアドレスを入力してください")
@NotBlank
private String email;
@Min(value = 18, message = "年齢は18歳以上である必要があります")
@Max(value = 120, message = "年齢は120歳以下である必要があります")
private Integer age;
@Past(message = "生年月日は過去の日付である必要があります")
private LocalDate birthDate;
@Pattern(regexp = "^\\d{3}-\\d{4}-\\d{4}$", message = "電話番号の形式が正しくありません(例:090-1234-5678)")
private String phoneNumber;
// ネストしたオブジェクトのバリデーション
@Valid
@NotNull
private Address address;
// コンテナ要素のバリデーション(Bean Validation 2.0+)
@NotEmpty(message = "タグは最低1つ必要です")
private List<@NotBlank String> tags;
// コンストラクタ、ゲッター、セッター
public User() {}
public User(String name, String email, Integer age) {
this.name = name;
this.email = email;
this.age = age;
}
// ゲッター・セッター省略
}
public class Address {
@NotBlank(message = "郵便番号は必須です")
@Pattern(regexp = "^\\d{3}-\\d{4}$", message = "郵便番号の形式が正しくありません(例:123-4567)")
private String zipCode;
@NotBlank(message = "都道府県は必須です")
private String prefecture;
@NotBlank(message = "市区町村は必須です")
private String city;
@NotBlank(message = "住所は必須です")
private String street;
// コンストラクタ、ゲッター、セッター省略
}
バリデーション実行と詳細エラーハンドリング
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import java.util.Set;
public class ValidationExample {
private static final Validator validator;
static {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
public static void main(String[] args) {
// 無効なデータでテスト
User invalidUser = new User();
invalidUser.setName(""); // 空文字
invalidUser.setEmail("invalid-email"); // 無効なメール
invalidUser.setAge(15); // 18歳未満
invalidUser.setPhoneNumber("12345"); // 無効な電話番号形式
// バリデーション実行
Set<ConstraintViolation<User>> violations = validator.validate(invalidUser);
if (!violations.isEmpty()) {
System.out.println("バリデーションエラーが発生しました:");
for (ConstraintViolation<User> violation : violations) {
System.out.printf("プロパティ: %s%n", violation.getPropertyPath());
System.out.printf("無効な値: %s%n", violation.getInvalidValue());
System.out.printf("メッセージ: %s%n", violation.getMessage());
System.out.println("---");
}
}
// 正常なデータでテスト
User validUser = new User();
validUser.setId(1L);
validUser.setName("田中太郎");
validUser.setEmail("[email protected]");
validUser.setAge(30);
validUser.setBirthDate(LocalDate.of(1993, 5, 15));
validUser.setPhoneNumber("090-1234-5678");
Address address = new Address();
address.setZipCode("123-4567");
address.setPrefecture("東京都");
address.setCity("渋谷区");
address.setStreet("道玄坂1-2-3");
validUser.setAddress(address);
validUser.setTags(List.of("開発者", "Java"));
Set<ConstraintViolation<User>> validViolations = validator.validate(validUser);
if (validViolations.isEmpty()) {
System.out.println("バリデーション成功!");
}
}
// 特定のプロパティのみバリデーション
public static void validateProperty() {
User user = new User();
Set<ConstraintViolation<User>> emailViolations =
validator.validateProperty(user, "email");
if (!emailViolations.isEmpty()) {
System.out.println("メールアドレスにエラーがあります:");
emailViolations.forEach(violation ->
System.out.println(violation.getMessage()));
}
}
}
Spring Boot統合とカスタムバリデーター
// Spring Boot Controllerでのバリデーション
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody User user, BindingResult result) {
if (result.hasErrors()) {
Map<String, String> errors = new HashMap<>();
result.getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
return ResponseEntity.badRequest().body(errors);
}
// ユーザー作成処理
return ResponseEntity.ok("ユーザーが正常に作成されました");
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable @Positive Long id) {
// ID取得処理
return ResponseEntity.ok(new User());
}
}
// カスタムバリデーター注釈の定義
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = JapanesePhoneNumberValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface JapanesePhoneNumber {
String message() default "日本の電話番号形式が正しくありません";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// カスタムバリデーターの実装
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
public class JapanesePhoneNumberValidator implements ConstraintValidator<JapanesePhoneNumber, String> {
private static final Pattern PHONE_PATTERN = Pattern.compile(
"^(070|080|090)-\\d{4}-\\d{4}$|^0\\d{1,4}-\\d{1,4}-\\d{4}$"
);
@Override
public void initialize(JapanesePhoneNumber constraintAnnotation) {
// 初期化処理(必要に応じて)
}
@Override
public boolean isValid(String phoneNumber, ConstraintValidatorContext context) {
if (phoneNumber == null) {
return true; // null許可は@NotNullで制御
}
boolean isValid = PHONE_PATTERN.matcher(phoneNumber).matches();
if (!isValid) {
// カスタムエラーメッセージの設定
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(
"電話番号は日本の形式で入力してください(例:090-1234-5678、03-1234-5678)"
).addConstraintViolation();
}
return isValid;
}
}
// カスタムバリデーターの使用
public class Contact {
@JapanesePhoneNumber
private String phoneNumber;
// その他のフィールド、コンストラクタ、ゲッター、セッター
}
グループバリデーションとメソッドバリデーション
// バリデーショングループの定義
public interface CreateGroup {}
public interface UpdateGroup {}
public class Product {
@NotNull(groups = UpdateGroup.class, message = "更新時はIDが必要です")
private Long id;
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class}, message = "商品名は必須です")
@Size(min = 1, max = 100, groups = {CreateGroup.class, UpdateGroup.class})
private String name;
@NotNull(groups = {CreateGroup.class, UpdateGroup.class}, message = "価格は必須です")
@PositiveOrZero(groups = {CreateGroup.class, UpdateGroup.class}, message = "価格は0以上である必要があります")
private BigDecimal price;
@NotBlank(groups = CreateGroup.class, message = "作成時はカテゴリが必要です")
private String category;
// コンストラクタ、ゲッター、セッター
}
// グループバリデーションの実行
@Service
@Validated
public class ProductService {
@Autowired
private Validator validator;
// メソッドパラメータのバリデーション
public Product createProduct(@Validated(CreateGroup.class) Product product) {
// 作成処理
return product;
}
public Product updateProduct(@Validated(UpdateGroup.class) Product product) {
// 更新処理
return product;
}
// メソッド戻り値のバリデーション
@Valid
public Product getProductById(@NotNull @Positive Long id) {
// 商品取得処理
return new Product();
}
// 手動でのグループバリデーション
public void validateProductForCreate(Product product) {
Set<ConstraintViolation<Product>> violations =
validator.validate(product, CreateGroup.class);
if (!violations.isEmpty()) {
StringBuilder sb = new StringBuilder("バリデーションエラー:\n");
violations.forEach(violation ->
sb.append(violation.getPropertyPath())
.append(": ")
.append(violation.getMessage())
.append("\n"));
throw new IllegalArgumentException(sb.toString());
}
}
}
国際化とエラーメッセージカスタマイズ
// messages.properties (デフォルト - 英語)
user.name.notblank=Name is required
user.email.invalid=Please enter a valid email address
user.age.range=Age must be between {min} and {max}
// messages_ja.properties (日本語)
user.name.notblank=名前は必須です
user.email.invalid=有効なメールアドレスを入力してください
user.age.range=年齢は{min}歳以上{max}歳以下で入力してください
// messages_en.properties (英語)
user.name.notblank=Name is required
user.email.invalid=Please enter a valid email address
user.age.range=Age must be between {min} and {max}
// カスタムメッセージを使用するエンティティ
public class InternationalUser {
@NotBlank(message = "{user.name.notblank}")
private String name;
@Email(message = "{user.email.invalid}")
private String email;
@Min(value = 18, message = "{user.age.range}")
@Max(value = 120, message = "{user.age.range}")
private Integer age;
// ゲッター、セッター
}
// プログラムによるメッセージ国際化
import jakarta.validation.MessageInterpolator;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
public class InternationalValidation {
public static void validateWithLocale(Object obj, Locale locale) {
ValidatorFactory factory = Validation.byDefaultProvider()
.configure()
.messageInterpolator(new ResourceBundleMessageInterpolator(
new PlatformResourceBundleLocator("messages")))
.buildValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Object>> violations = validator.validate(obj);
violations.forEach(violation -> {
// 指定されたロケールでメッセージを表示
String message = violation.getMessage();
System.out.println(message);
});
}
}
Jakarta EE環境での高度な統合
// CDI環境でのバリデーターの注入
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.validation.Validator;
@ApplicationScoped
public class UserValidationService {
@Inject
private Validator validator;
public boolean isValidUser(User user) {
Set<ConstraintViolation<User>> violations = validator.validate(user);
return violations.isEmpty();
}
public List<String> getValidationErrors(User user) {
return validator.validate(user)
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
}
}
// JAX-RSでのバリデーション統合
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.validation.Valid;
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
@POST
public Response createUser(@Valid User user) {
// Javaのバリデーションが自動実行される
// バリデーションエラーがあると400エラーが自動返却
return Response.ok().entity("User created successfully").build();
}
@PUT
@Path("/{id}")
public Response updateUser(
@PathParam("id") @Positive Long id,
@Valid User user) {
return Response.ok().entity("User updated successfully").build();
}
}
// JPA環境でのエンティティバリデーション
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
@Entity
@Table(name = "users")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
@NotBlank(message = "ユーザー名は必須です")
@Size(min = 2, max = 100)
private String username;
@Column(nullable = false, unique = true, length = 255)
@Email(message = "有効なメールアドレスを入力してください")
@NotBlank
private String email;
@Column(nullable = false)
@PastOrPresent(message = "作成日は現在または過去の日付である必要があります")
private LocalDateTime createdAt;
// JPAライフサイクルコールバック
@PrePersist
@PreUpdate
public void validateEntity() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<UserEntity>> violations = validator.validate(this);
if (!violations.isEmpty()) {
StringBuilder sb = new StringBuilder("エンティティバリデーションエラー: ");
violations.forEach(violation ->
sb.append(violation.getPropertyPath())
.append("=")
.append(violation.getMessage())
.append("; "));
throw new IllegalStateException(sb.toString());
}
}
// コンストラクタ、ゲッター、セッター
}