Validation
SeedStack provides built-in support for Bean Validation 1.1 (JSR 303 & JSR 349) in the core module. This is a part of the core, including the Hibernate Validator implementation which is automatically provided.
The following features are provided:
- On-demand POJO validation,
- Automatic validation of method parameters and return values (through interception),
- Dependency injection inside custom validator,
- Validation errors message interpolation using EL if present, with fallback to standard parameter resolver.
Validation can also happen at various stages of an object lifecycle, depending on the other SeedStack modules integrated in a project. For instance, persistence add-ons often support automatic validation of objects before persisting them. These additional validations will use the same validation behavior as the one described here.
Usage
On-demand
On-demand validation is done by injecting the standard Validator
interface:
public class SomeClass {
@Inject
private Validator validator;
public void someMethod(SomePojo somePojo) {
Set<ConstraintViolation<SomePojo>> violations = validator.validate(somePojo);
// Inspect constraint violations
}
}
For more advanced use cases the ValidatorFactory
interface can also be injected. It allows
to control the Validator
creation process.
Method parameters and return values
Method parameters and return values can be automatically validated by applying constraint annotations on them
(NotNull
, Valid
, …). These annotations
are automatically detected by SeedStack and interception will be enabled on the method to enforce them.
public class SomeClass {
@NotNull
public SomePojo someMethod(@Valid OtherPojo param) {
// At this point, param is guaranteed to be valid
return new SomePojo();
// The return value will be validated before being handed-over to the caller
}
}
A ConstraintViolationException
will be thrown on method call if at least a constraint
is violated.
Beware of SeedStack method interception limitations when using parameters and return values constraints.
Custom validators
Custom validators can be defined in two steps:
- First creating an annotation representing the constraint,
- Then creating its validator implementation.
Constraint annotation
To create a constraint annotation, create an annotation, itself annotated with @Constraint
referencing the custom validator implementation:
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {
// Required by specification
String message() default "{org.some.app.message}";
// Required by specification
Class<?>[] groups() default {};
// Required by specification
Class<? extends Payload>[] payload() default {};
CaseMode value();
// Required by specification to allow specifying
// multiple annotations on the same element
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@interface List {
CheckCase[] value();
}
}
The related CaseMode
enum is:
public enum CaseMode {
UPPER,
LOWER;
}
Validator implementation
To create the validator implementation, implement the ConstraintValidator
interface:
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode caseMode;
@Override
public void initialize(CheckCase constraintAnnotation) {
this.caseMode = constraintAnnotation.value();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintContext) {
if (value == null) {
return true;
} else if (caseMode == CaseMode.UPPER) {
return value.equals(value.toUpperCase());
} else {
return value.equals(value.toLowerCase());
}
}
}
Validator implementations can be injected to allow for sophisticated validation behavior.
For instance, you can access persistence, use business services or even check security conditions in a custom validator.