All You Need Is

Bean Validation の @Pattern の仕様

2024/08/12
Javaプログラミング

はじめに

今回は Java の Bean Validation の @Pattern の仕様について説明する。誤った例を使っている解説が多く、それを参照したコードをレビューで見て疲弊する日々を過しているため、世の中に正しい情報が提供されることを期待するのは諦めて自ら発信していくことにした。

先日の記事もその一環の中の一つである。

TL;DL

誤った実装

Google で Spring Boot を使ってリクエストに @Pattern アノテーションを使い正規表現でバリデーションをかける方法を検索すると次のような例をよく見る。

1
@Value
2
@Builder
3
public class Example {
4
5
@Pattern(regexp = "^[A-Z][a-z]+$")
6
String name = "Name";
7
}

^$ の両方、もしくはいずれかを使っている例も見られるが ^<正規表現>$ という形式で @Patternregexp に正規表現を記述している。これは正しく動作するため、誤った実装ではないが ^$ を使う必要はない。

^$ をわざわざ付けている人は @Pattern の仕様を理解していない可能性がある。

Jakarta Bean Validation

Java でバリデーションを実装したい場合は、Jakarta Bean Validation に準拠したライブラリを使うのが一般的でだろう。 Jakarta Bean Validation は、JSR 303JSR 349, JSR 380 で定められた Bean Validation が移管され、 Eclipse Foundation により管理されている Bean Validation について定めた仕様である 1

Java では Jakarta Bean Validation のように仕様のみが独立して定められ、それを実装するライブラリが複数存在することがある。例えば、Jakarta Bean Validation のリファレンス実装として Hibernate Validator がある。 Hibernate Validator を使うことが多いが、Jakarta Bean Validation に準拠した実装には Apache BVal もある。

Hibernate Validator

PatternValidator

@Pattern アノテーションのバリデーションは PatternValidator で行われる。

1
/*
2
* Hibernate Validator, declare and validate application constraints
3
*
4
* License: Apache License, Version 2.0
5
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6
*/
7
package org.hibernate.validator.internal.constraintvalidators.bv;
8
9
import java.lang.invoke.MethodHandles;
10
import java.util.regex.Matcher;
11
import java.util.regex.PatternSyntaxException;
12
import jakarta.validation.ConstraintValidator;
13
import jakarta.validation.ConstraintValidatorContext;
14
import jakarta.validation.constraints.Pattern;
15
16
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
17
import org.hibernate.validator.internal.engine.messageinterpolation.util.InterpolationHelper;
18
import org.hibernate.validator.internal.util.logging.Log;
19
import org.hibernate.validator.internal.util.logging.LoggerFactory;
20
21
/**
22
* @author Hardy Ferentschik
23
*/
24
public class PatternValidator implements ConstraintValidator<Pattern, CharSequence> {
25
26
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
27
28
private java.util.regex.Pattern pattern;
29
private String escapedRegexp;
30
31
@Override
32
public void initialize(Pattern parameters) {
33
Pattern.Flag[] flags = parameters.flags();
34
int intFlag = 0;
35
for ( Pattern.Flag flag : flags ) {
36
intFlag = intFlag | flag.getValue();
37
}
38
39
try {
40
pattern = java.util.regex.Pattern.compile( parameters.regexp(), intFlag );
41
}
42
catch (PatternSyntaxException e) {
43
throw LOG.getInvalidRegularExpressionException( e );
44
}
45
46
escapedRegexp = InterpolationHelper.escapeMessageParameter( parameters.regexp() );
47
}
48
49
@Override
50
public boolean isValid(CharSequence value, ConstraintValidatorContext constraintValidatorContext) {
51
if ( value == null ) {
52
return true;
53
}
54
55
if ( constraintValidatorContext instanceof HibernateConstraintValidatorContext ) {
56
constraintValidatorContext.unwrap( HibernateConstraintValidatorContext.class ).addMessageParameter( "regexp", escapedRegexp );
57
}
58
59
Matcher m = pattern.matcher( value );
60
return m.matches();
61
}
62
}
org/hibernate/validator/internal/constraintvalidators/bv/PatternValidator.java

検証成功するかどうかは Mather#matches メソッドで判定される。 Matcher#matches は、文字列全体が正規表現に一致するかどうかを判定するメソッドである。一部のみが一致している場合は検証は失敗する。そのため、@Pattern アノテーションでは ^$ を使わなくても文字列全体が正規表現に一致すれば検証は成功する。

正しい (?) 実装

^$ を付けても動作としては変わらないが、ただのノイズでしかないため、不要な記述は避けるべきだろう。よって、始めに見た実装例は次のように書き換えることができる。

1
@Value
2
@Builder
3
public class Example {
4
5
@Pattern(regexp = "[A-Z][a-z]+")
6
String name = "Name";
7
}

おわりに

@Patternregexp に正規表現を指定する際に ^$ を付ける必要はない。昔に書かれた記事では ^$ を使う例はないが、最近書かれた記事では ^$ を付けているものが多いように思う。

ある時点で誤った情報が書かれてしまい、ネットで見つけたコードをそのまま使う人が多いということだろう。ネットで見つけた情報を鵜呑みにせず、公式ドキュメントを読んで仕様を確認することが大切。

Footnotes

  1. Java EE と Jakarta EE については Java EE から Jakarta EE へ を参照。


Buy Me A Coffee