You need this when your domain classes already carry Jakarta Bean Validation annotations such as @NotNull and @Size, and you want the form to enforce them instead of repeating each rule with Wicket validators.
Add the dependencies
Add wicket-bean-validation and a Bean Validation provider. Wicket does not ship a provider; Hibernate Validator is the common choice. It also needs a Jakarta Expression Language implementation; its documentation names the one that matches your version.
pom.xml
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-bean-validation</artifactId>
<version>${wicket.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
Turn it on in the application
WicketApplication.java
@Override
protected void init() {
super.init();
new BeanValidationConfiguration().configure(this);
}
Annotate the class
Person.java
public class Person implements Serializable {
@NotNull
@Size(max = 50)
private String name;
@Email
private String email;
@Min(18)
private int age;
@Past
private LocalDate birthday;
@Pattern(regexp = "\\d{4}\\s?[A-Z]{2}", message = "{person.invalidPostcode}")
private String postcode;
// getters and setters
}
Add a PropertyValidator to each field
A PropertyValidator finds the property behind the field and runs that property’s constraints on the converted input. It reads the property from a PropertyModel or from an inherited CompoundPropertyModel:
PersonPage.java
Form<Person> form = new Form<>("form", new CompoundPropertyModel<>(new Person()));
add(form);
form.add(new TextField<String>("name").add(new PropertyValidator<>()));
form.add(new TextField<String>("email").add(new PropertyValidator<>()));
form.add(new TextField<Integer>("age").add(new PropertyValidator<>()));
form.add(new TextField<LocalDate>("birthday").add(new PropertyValidator<>()));
form.add(new TextField<String>("postcode").add(new PropertyValidator<>()));
form.add(new FeedbackPanel("feedback"));
The validator also changes the field to match its constraints:
-
@NotNulland@NotEmptymake the field required. -
@Size(max = ...)adds amaxlengthattribute to the input.
Validate a field bound to a lambda model
A lambda model does not tell which property it reads, so name it with a Property:
TextField<String> name = new TextField<>("name",
LambdaModel.of(person, Person::getName, Person::setName));
name.add(new PropertyValidator<>(new Property(Person.class, "name")));
Change the message text
Wicket ships messages for the standard constraints, keyed by the constraint’s message key, such as jakarta.validation.constraints.NotNull.message. They use ${label} like the other form messages. Define the same key in a properties file of your page or application to replace one.
For a message of your own, put a key in braces in the annotation, as {person.invalidPostcode} above, and define that key:
PersonPage.properties
person.invalidPostcode=${label} must be a Dutch postcode, such as 1234 AB.
Wicket tries the key with the type of the rejected value appended first (person.invalidPostcode.String), then the key itself. The attributes of the annotation are available as variables, so a message for @Size can use ${max}.
Validate only some constraints
Put constraints in a group with a marker interface, and pass the group to the validator to check only the constraints in it:
public interface OnSignup {}
@NotNull(groups = OnSignup.class)
private String email;
emailField.add(new PropertyValidator<>(OnSignup.class));
Constraints without a group belong to jakarta.validation.groups.Default; pass it as well when you want those checked too.
Constraints on the whole object
PropertyValidator checks one property at a time, before the model is updated. A constraint that compares properties, declared on the class, needs the updated object. Check it in onValidateModelObjects() of the form, which runs after the model update; see How a form is processed for what that means for your model.