Java Bean Validation
The TODAY Framework provides support for the Java Bean Validation API.
Overview of Bean Validation
Bean Validation provides a common way of validation through constraint declaration and metadata for Java applications. To use it, you annotate domain model properties with declarative validation constraints which are then enforced by the runtime. There are built-in constraints, and you can also define your own custom constraints.
Consider the following example, which shows a simple PersonForm
model with two properties:
-
Java
public class PersonForm {
private String name;
private int age;
}
Bean Validation lets you declare constraints as the following example shows:
-
Java
public class PersonForm {
@NotNull
@Size(max=64)
private String name;
@Min(0)
private int age;
}
A Bean Validation validator then validates instances of this class based on the declared constraints. See Bean Validation for general information about the API. See the Hibernate Validator documentation for specific constraints. To learn how to set up a bean validation provider as a Infra bean, keep reading.
Configuring a Bean Validation Provider
Infra provides full support for the Bean Validation API including the bootstrapping of a
Bean Validation provider as a Infra bean. This lets you inject a
jakarta.validation.ValidatorFactory
or jakarta.validation.Validator
wherever validation
is needed in your application.
You can use the LocalValidatorFactoryBean
to configure a default Validator as a Infra
bean, as the following example shows:
-
Java
-
XML
import cn.taketoday.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class AppConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
<bean id="validator" class="cn.taketoday.validation.beanvalidation.LocalValidatorFactoryBean"/>
The basic configuration in the preceding example triggers bean validation to initialize by using its default bootstrap mechanism. A Bean Validation provider, such as the Hibernate Validator, is expected to be present in the classpath and is automatically detected.
Inject Jakarta Validator
LocalValidatorFactoryBean
implements both jakarta.validation.ValidatorFactory
and
jakarta.validation.Validator
, so you can inject a reference to the latter to
apply validation logic if you prefer to work with the Bean Validation API directly,
as the following example shows:
-
Java
import jakarta.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
Inject Infra Validator
In addition to implementing jakarta.validation.Validator
, LocalValidatorFactoryBean
also adapts to cn.taketoday.validation.Validator
, so you can inject a reference
to the latter if your bean requires the Infra Validation API.
For example:
-
Java
import cn.taketoday.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
When used as cn.taketoday.validation.Validator
, LocalValidatorFactoryBean
invokes the underlying jakarta.validation.Validator
, and then adapts
ContraintViolation
s to FieldError
s, and registers them with the Errors
object
passed into the validate
method.
Configure Custom Constraints
Each bean validation constraint consists of two parts:
-
A
@Constraint
annotation that declares the constraint and its configurable properties. -
An implementation of the
jakarta.validation.ConstraintValidator
interface that implements the constraint’s behavior.
To associate a declaration with an implementation, each @Constraint
annotation
references a corresponding ConstraintValidator
implementation class. At runtime, a
ConstraintValidatorFactory
instantiates the referenced implementation when the
constraint annotation is encountered in your domain model.
By default, the LocalValidatorFactoryBean
configures a InfraConstraintValidatorFactory
that uses Infra to create ConstraintValidator
instances. This lets your custom
ConstraintValidators
benefit from dependency injection like any other Infra bean.
The following example shows a custom @Constraint
declaration followed by an associated
ConstraintValidator
implementation that uses Infra for dependency injection:
-
Java
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
-
Java
import jakarta.validation.ConstraintValidator;
public class MyConstraintValidator implements ConstraintValidator {
@Autowired;
private Foo aDependency;
// ...
}
As the preceding example shows, a ConstraintValidator
implementation can have its dependencies
@Autowired
as any other Infra bean.
Infra-driven Method Validation
You can integrate the method validation feature of Bean Validation into a
Infra context through a MethodValidationPostProcessor
bean definition:
-
Java
-
XML
import cn.taketoday.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class AppConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
<bean class="cn.taketoday.validation.beanvalidation.MethodValidationPostProcessor"/>
To be eligible for Infra-driven method validation, target classes need to be annotated
with Infra @Validated
annotation, which can optionally also declare the validation
groups to use. See
MethodValidationPostProcessor
for setup details with the Hibernate Validator and Bean Validation providers.
Method validation relies on AOP Proxies around the target classes, either JDK dynamic proxies for methods on interfaces or CGLIB proxies. There are certain limitations with the use of proxies, some of which are described in Understanding AOP Proxies. In addition remember to always use methods and accessors on proxied classes; direct field access will not work. |
Web MVC and WebFlux have built-in support for the same underlying method validation but without the need for AOP. Therefore, do check the rest of this section, and also see the Web MVC Validation and Error Responses sections.
Method Validation Exceptions
By default, jakarta.validation.ConstraintViolationException
is raised with the set of
ConstraintViolation
s returned by jakarata.validation.Validator
. As an alternative,
you can have MethodValidationException
raised instead with ConstraintViolation
s
adapted to MessageSourceResolvable
errors. To enable set the following flag:
-
Java
-
XML
import cn.taketoday.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class AppConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setAdaptConstraintViolations(true);
return processor;
}
}
<bean class="cn.taketoday.validation.beanvalidation.MethodValidationPostProcessor">
<property name="adaptConstraintViolations" value="true"/>
</bean>
MethodValidationException
contains a list of ParameterValidationResult
s which
group errors by method parameter, and each exposes a MethodParameter
, the argument
value, and a list of MessageSourceResolvable
errors adapted from
ConstraintViolation
s. For @Valid
method parameters with cascaded violations on
fields and properties, the ParameterValidationResult
is ParameterErrors
which
implements cn.taketoday.validation.Errors
and exposes validation errors as
FieldError
s.
Customizing Validation Errors
The adapted MessageSourceResolvable
errors can be turned into error messages to
display to users through the configured MessageSource
with locale and language specific
resource bundles. This section provides an example for illustration.
Given the following class declarations:
-
Java
record Person(@Size(min = 1, max = 10) String name) {
}
@Validated
public class MyService {
void addStudent(@Valid Person person, @Max(2) int degrees) {
// ...
}
}
A ConstraintViolation
on Person.name()
is adapted to a FieldError
with the following:
-
Error codes
"Size.student.name"
,"Size.name"
,"Size.java.lang.String"
, and"Size"
-
Message arguments
"name"
,10
, and1
(the field name and the constraint attributes) -
Default message "size must be between 1 and 10"
To customize the default message, you can add properties to
MessageSource
resource bundles using any of the above errors codes and message arguments. Note also that the
message argument "name"
is itself a MessagreSourceResolvable
with error codes
"student.name"
and "name"
and can customized too. For example:
- Properties
-
Size.student.name=Please, provide a {0} that is between {2} and {1} characters long student.name=username
A ConstraintViolation
on the degrees
method parameter is adapted to a
MessageSourceResolvable
with the following:
-
Error codes
"Max.myService#addStudent.degrees"
,"Max.degrees"
,"Max.int"
,"Max"
-
Message arguments "degrees2 and 2 (the field name and the constraint attribute)
-
Default message "must be less than or equal to 2"
To customize the above default message, you can add a property such as:
- Properties
-
Max.degrees=You cannot provide more than {1} {0}
Additional Configuration Options
The default LocalValidatorFactoryBean
configuration suffices for most
cases. There are a number of configuration options for various Bean Validation
constructs, from message interpolation to traversal resolution. See the
LocalValidatorFactoryBean
javadoc for more information on these options.
Configuring a DataBinder
You can configure a DataBinder
instance with a Validator
. Once configured, you can
invoke the Validator
by calling binder.validate()
. Any validation Errors
are
automatically added to the binder’s BindingResult
.
The following example shows how to use a DataBinder
programmatically to invoke validation
logic after binding to a target object:
-
Java
Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());
// bind to the target object
binder.bind(propertyValues);
// validate the target object
binder.validate();
// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
You can also configure a DataBinder
with multiple Validator
instances through
dataBinder.addValidators
and dataBinder.replaceValidators
. This is useful when
combining globally configured bean validation with a Infra Validator
configured
locally on a DataBinder instance. See
Web MVC Validation Configuration.
Web MVC 3 Validation
See Validation in the Web MVC chapter.