Validator

User Input Validation

User input validation is an indispensable function of a web application. ZK's validator can help developers accomplish this task. The validator is a reusable element that performs validation and stores validation messages into a validation message holder. When it is applied, it is invoked before saving the data to the binding target (ViewModel or middle object). When you bind a component's attribute to a validator, the binder will use it to validate attribute's value automatically before saving to a ViewModel or to a middle object. If validation fails, ViewModel's (or middle object's) properties will be unchanged.

We usually provide custom validator with ViewModel's property.

public void class MyViewModel {
    private Validator emailValidator = new EmailValidator();

    public Validator getEmailValidator() {
        return emailValidator;
    }
}

Then it can be referenced on a ZUL by ViewModel's properties or a string literal with validator's full-qualified class name.

<textbox value="@save(vm.account.email) @validator(vm.emailValidator)"/>

<textbox value="@save(vm.account.email) @validator('org.foo.EmailValidator')"/>
  • Binder use vm.emailValidtor to validate textbox's value before saving to vm.account.email. If validation fails, ViewModel's property: vm.account.email will remain unchanged.
    <textbox value="@save(vm.account.email, before='save') @validator(vm.emailValidator)"/>
    
  • Binder uses vm.emailValidtor to validate textbox's value before executing Command 'save'.

Following is a comparison table for the two saving syntaxes mentioned above:

Property Binding Save Before Command
Syntax Example @bind(vm.account.email)<br>@validator(vm.emailValidator) @load(vm.account.email)<br>@save(vm.account.email, before='save')<br>@validator(vm.emailValidator)
Save When a component's attribute related event fires (e.g. onChange for value) Before executing a command
Validation Timing
  • Immediately validate for single field
  • No validation when executing a command
  • Batch validate all fields when triggering specified command
  • No immediate validation of single fields after a user input
Validation Fails Not save data to ViewModel Not save data to ViewModel &
Not execute the command

In form-binding, you can apply a validator to “form” attribute or an input component. If you apply it to both places, you can double validate user input. The first time is when saving the data to a middle object; this can provide the user with an immediate response after input. The second time is when saving to a ViewModel upon a command; this can validate user input even if he doesn't input anything and submit empty data directly.

<toolbar>
    <button label="Save" onClick="@command('saveOrder')" disabled="@bind(empty vm.selected)" />
</toolbar>
<groupbox form="@id('fx') @load(vm.selected) @save(vm.selected, before='saveOrder') @validator(vm.formValidator)">
    <grid hflex="true" >
        <columns>
            <column width="120px"/>
            <column/>
        </columns>
        <rows>
            <row>
                Id <label value="@load(fx.id)"/>
            </row>
            <row>
                Description <textbox value="@bind(fx.description)"/>
            </row>
            <row>
                Quantity <intbox value="@bind(fx.quantity) @validator(vm.quantityValidator)"/>
            </row>
            <row>
                Price <doublebox value="@bind(fx.price) @validator(vm.priceValidator)" format="###,##0.00" />
            </row>
        </rows>
    </grid>
</groupbox>
  • Line 4: ZK will invoke vm.formValidator before executing command 'saveOrder'.
  • Line 18: ZK will validate input value when onChange event fires on intbox (when a user blur the focus).
  • Line 4, 18, 21: You can apply validators on form attribute and each input component respectively to perform double validation.

Validation Message Holder

Databinding provides a standard mechanism to store and display validation messages. After performing validation, the validator might store a validation message in validation message holder. To use it, you have to initialize it by specifying its id in validationMessages attribute with the @id. Then you can reference it with this id.

<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('foo.MyViewModel')"
    validationMessages="@id('vmsgs')">
</window>

Displaying a Message

Validation message holder stores messages like a map with key-value pairs. The value is the validation messages generated by validators after the validation is performed, and the default key is the binding source component (object itself, no id) that is bound to validators. To retrieve and display validation messages in a ZUL, you can bind a display component, i.e. label, to validation message holders with a component as the key.

The binder will reload validation messages each time after validation.

Displaying a Validation Message with Default Key

<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('foo.MyViewModel')"
    validationMessages="@id('vmsgs')">
    <hlayout>
        Value1:
        <textbox id="tb1" value="@bind(vm.value1) @validator(vm.validator1)"/>
        <label id="m1" value="@bind(vmsgs[tb1])"/>
    </hlayout>
    <hlayout>
        Value2:
        <intbox id="tb2" value="@bind(vm.value2) @validator(vm.validator2)"/>
        <label id="m2" value="@bind(vmsgs[self.previousSibling])"/>
    </hlayout>
</window>
  • You can use component's id to reference a component object. (line 6)
  • You can use component's property to reference a component object with relative position; therefore, it is not necessary to give component an id.(line 11)

Displaying a Message of a Self-defined Key

You can display the first validation message which is bound to a self-defined key of a validator that extends AbstractValidator. Display validation message with self-defined key

<vbox form="@id('fx') @load(vm) @save(vm, before='submit') @validator(vm.formValidator)">
    <hbox>
        <textbox id="t41" value="@bind(fx.value1)"/>
        <label id="l41" value="@bind(vmsgs['fkey1'])"/>
    </hbox>
    <hbox>
        <textbox id="t42" value="@bind(fx.value2)"/>
        <label id="l42" value="@bind(vmsgs['fkey2'])"/>
    </hbox>
    <hbox>
        <textbox id="t43" value="@bind(fx.value3)"/>
        <label id="l43" value="@bind(vmsgs['fkey3'])"/>
    </hbox>
    <button id="submit" label="submit" onClick="@command('submit')"/>
</vbox>
  • You can use self-defined key to display a certain message. (line 4)

Please read Self-defined Validation Message Key for the detail about the validator.

Displaying Multiple Messages

Available for ZK: EE

Since 6.0.1

A validator can set multiple messages for a component or a self-defined key. You can get all messages from Validation Message Holder's special property texts .[1]

[1]: In EL, vmsgs.texts is the same as vmsgs['texts'], so you should avoid using texts as your self-defined key.

Displaying Multiple Messages of a Form Validator

<div id="formdiv" form="... @validator('fooValidator')">
    <!-- other components -->
</div>
<grid id="msggrid" model="@bind(vmsgs.texts[formdiv])" visible="@bind(not empty vmsgs.texts[formdiv])">
    <template name="model" var="msg">
        <row>
            <label value="@bind(msg)" />
        </row>
    </template>
</grid>
  • Using a Grid to Display Multiple Messages. (line 4)

You can also get all messages of the Validation Message Holder with syntax @bind(vmsgs.texts) and get messages of a self-defined key with syntax @bind(vmsgs.texts['a_self_defined_key']).

Implementing a Validator

You can create custom validators upon your application's requirement by implementing Validator interface or inheriting AbstractValidator for convenience.

Property Validator

Single Property Validation

We usually need to validate one property at a time; the simplest way is to inherit AbstractValidator and override validate() to implement a custom validation rule. If validation fails, use addInvalidMessage() to store the validation messages to be displayed. Validator bound component object must be passed as a key, in order to retrieve this message like we mentioned in section Validation Message Holder.

<intbox value="@save(vm.quantity) @validator(vm.rangeValidator)"/>
public Validator getRangeValidator() {
    return new AbstractValidator() {
        public void validate(ValidationContext ctx) {
            Integer val = (Integer) ctx.getProperty().getValue();
            if (val < 10 || val > 100) {
                addInvalidMessage(ctx, "value must not < 10 or > 100, but is " + val);
            }
        }
    };
}
  • Line 4: We can get the user input data from validator's binding source component by ctx.getProperty().getValue().
  • Line 6: addInvalidMessage() will add message into validation message holder with the default key.

Dependent Property Validation

We sometimes need another property's value to validate the current property. We have to save those values that have dependency among them upon the same Command (use @save(vm.p, before='command') ; thus, the binder will pass those properties that are saved as the same command to ValidationContext and we can retrieve them with property's key to process.

Assume the shipping date to be at least 3 days later than the creation date.

<window apply="org.zkoss.bind.BindComposer"
    viewModel="@id('vm') @init('eg.ValidationMessagesVM')"
    validationMessages = "@id('vmsgs')">
    <grid hflex="true" >
        <columns>
            <column width="120px"/>
            <column/>
        </columns>
        <rows>
            <!-- other input fields -->
            <row>
                Creation Date
                <hlayout>
                    <datebox id="cdBox" value="@load(vm.selected.creationDate)
                        @save(vm.selected.creationDate, before='saveOrder')
                        @validator(vm.creationDateValidator)"/>
                    <label value="@load(vmsgs[cdBox])" sclass="red" />
                </hlayout>
            </row>
            <row>
                Shipping Date
                <hlayout>
                    <datebox id="sdBox" value="@load(vm.selected.shippingDate)
                        @save(vm.selected.shippingDate, before='saveOrder')
                        @validator(vm.shippingDateValidator)"/>
                    <label value="@load(vmsgs[sdBox])" sclass="red" />
                </hlayout>
            </row>
        </rows>
    </grid>
</window>
  • As we save vm.selected.creationDate and vm.selected.shippingDate before Command 'saveOrder', they will be passed into validation context.

Our custom shipping date validator should get the creation date to compare.

public class ShippingDateValidator extends AbstractValidator{

    public void validate(ValidationContext ctx) {
        Date shipping = (Date) ctx.getProperty().getValue();//the main property
        Date creation = (Date) ctx.getProperties("creationDate")[0].getValue();//the collected
        //multiple fields dependent validation, shipping date have to large than creation more than 3 days.
        if (!isDayAfter(creation, shipping, 3)) {
            addInvalidMessage(ctx, "must large than creation date at least 3 days");
        }
    }

    static public boolean isDayAfter(Date date, Date laterDay, int day) {
        if (date == null) return false;
        if (laterDay == null) return false;

        Calendar cal = Calendar.getInstance();
        Calendar lc = Calendar.getInstance();

        cal.setTime(date);
        lc.setTime(laterDay);

        int cy = cal.get(Calendar.YEAR);
        int ly = lc.get(Calendar.YEAR);

        int cd = cal.get(Calendar.DAY_OF_YEAR);
        int ld = lc.get(Calendar.DAY_OF_YEAR);

        return (ly * 365 + ld) - (cy * 365 + cd) >= day;
    }
}
  • Shipping date is the main property to be validated. The way to retrieve other properties is different than the main one. (line 4)

Dependent Property Validator in Form Binding

If you want to validate a property according to another property's value in form binding, you would have to apply a validator to the form attribute instead of an input component that will bind to a middle object's property. Then you can get all properties from validation context.

The following is an example that demonstrates the same shipping date validator but used in the context of form-binding.

Using validator in form-binding

<toolbar>
    <button label="Save" onClick="@command('saveOrder')" disabled="@bind(empty vm.selected)"/>
</toolbar>
<groupbox form="@id('fx') @load(vm.selected) @save(vm.selected, before='saveOrder')
    @validator(vm.shippingDateValidator)">
    <grid hflex="true" >
        <columns>
            <column width="120px"/>
            <column/>
        </columns>
        <!-- other components -->
        <rows>
            <row>
                Creation Date
                <hlayout>
                    <datebox id="cdBox" value="@bind(fx.creationDate)
                        @validator(vm.creationDateValidator)"/>
                    <label value="@load(vmsgs[cdBox])" sclass="red" />
                </hlayout>
            </row>
            <row>
                Shipping Date
                <hlayout>
                    <datebox id="sdBox" value="@bind(fx.shippingDate)"/>
                    <label value="@load(vmsgs[sdBox])" sclass="red" />
                </hlayout>
            </row>
        </rows>
    </grid>
</groupbox>
  • Line 5: Applying a validator in form-binding; not an individual input component that binds to middle object's property.

Validator for Form-Binding

public Validator getShippingDateValidator() {
    return new AbstractValidator() {
        public void validate(ValidationContext ctx) {
            Date shipping = (Date) ctx.getProperties("shippingDate")[0].getValue();
            Date creation = (Date) ctx.getProperties("creationDate")[0].getValue();
            //dependent validation, shipping date have to later than creation date for more than 3 days.
            if (!CalendarUtil.isDayAfter(creation, shipping, 3)) {
                addInvalidMessage(ctx,"must large than creation date at least 3 days");
            }
        }
    };
}
  • Line 4~5: The value main property is a Form when applying to a form-binding. We should retrieve all properties with its key. Since 6.0.1, you can get values of the bean by ValidationContext.getProperties(Object)
public Validator getShippingDateValidator() {
    return new AbstractValidator() {
        public void validate(ValidationContext ctx) {
            //base of main property is the bean, you can get all properties of the bean
            Map<String,Property> beanProps = ctx.getProperties(ctx.getProperty().getBase());
            Date shipping = (Date) beanProps.get("shippingDate").getValue();
            Date creation = (Date) beanProps.get("creationDate").getValue();
            //....
        }
    };
}

Passing and Retrieving Parameters

We can pass one or more parameters by EL expression to a validator. The parameters are written in key-value pairs format inside @validator() annotation.

Passing a constant value in a ZUL

<textbox id="keywordBox" value="@save(vm.keyword) @validator(vm.maxLengthValidator, length=3)"/>

Retrieving Parameters in a Validator

public class MaxLengthValidator implements Validator {
    public void validate(ValidationContext ctx) {
        Number maxLength = (Number) ctx.getBindContext().getValidatorArg("length");
        if (ctx.getProperty().getValue() instanceof String) {
            String value = (String) ctx.getProperty().getValue();
            if (value.length() > maxLength.longValue()) {
                ctx.setInvalid();
            }
        } else {
            ctx.setInvalid();
        }
    }
}
  • The parameter's name “length” is user-defined.

Passing Object in a ZUL

<combobox id="upperBoundBox" >
    <comboitem label="90" value="90"/>
    <comboitem label="80" value="80"/>
    <comboitem label="70" value="70"/>
    <comboitem label="60" value="60"/>
    <comboitem label="50" value="50"/>
</combobox>
<intbox value="@save(vm.quantity)
    @validator(vm.upperBoundValidator, upper=upperBoundBox.selectedItem.value)"/>
  • Pass a value from another component's attribute.

Self-defined Validation Message Key

You might want to set the key of a validation message on your own, especially when you use a single validator to validate multiple fields. As all validation messages generated by one validator for a component have the same default key (the component object itself), to retrieve and display a validation message individually, you would have to set a different key for each message.

Assume you use only one validator in form binding to validate all fields.

public Validator getFormValidator() {
    return new AbstractValidator() {
        public void validate(ValidationContext ctx) {
            String val = (String) ctx.getProperties("value1")[0].getValue();
            if (invalidCondition01(val)) {
                addInvalidMessage(ctx, "fkey1", "value1 error");
            }
            val = (String) ctx.getProperties("value2")[0].getValue();
            if (invalidCondition02(val)) {
                addInvalidMessage(ctx, "fkey2", "value2 error");
            }
            val = (String) ctx.getProperties("value3")[0].getValue();
            if (invalidCondition03(val)) {
                addInvalidMessage(ctx, "fkey3", "value3 error");
            }
        }
    };
}
  • Because validation messages are stored in the same context, you should use different keys for different messages.

Displaying a Validation Message Next to Each Component

<vbox form="@id('fx') @load(vm) @save(vm, before='submit') @validator(vm.formValidator)">
    <hbox>
        <textbox id="t41" value="@bind(fx.value1)"/>
        <label id="l41" value="@bind(vmsgs['fkey1'])"/>
    </hbox>
    <hbox>
        <textbox id="t42" value="@bind(fx.value2)"/>
        <label id="l42" value="@bind(vmsgs['fkey2'])"/>
    </hbox>
    <hbox>
        <textbox id="t43" value="@bind(fx.value3)"/>
        <label id="l43" value="@bind(vmsgs['fkey3'])"/>
    </hbox>
    <button id="submit" label="submit" onClick="@command('submit')"/>
</vbox>

Validation in Non-Conditional Property-Binding

The execution of a non-conditional property binding is separated from execution of command. If an event triggers both property-saving and command execution, they will not block each other.

For example:

<textbox value="@bind(vm.value) @validator(vm.myValidator)" onChange="@command('submit')"/>
  • When onChange event fires, the binder will perform validation before saving vm.value. It doesn't matter if validation fails or suceeds; it will still execute command “submit”. Validation failure only stops binder from saving textbox's value into vm.value.

An example showing the opposite:

<textbox id="t1" value="@bind(vm.foo)" onChange="@command('submit')"/>
<textbox id="t2" value="@save(vm.bar, before='submit') @validator(vm.myValidator)"/>
  • When onChange event fires, if t2's value fails in validation, it will stop the binder from executing command “submit”. But the binder will still save t1's value to vm.foo.

Registering Application Level Validators

Available for ZK: EE

Since 6.0.1

You can register application level validators[1] by setting library-property(org.zkoss.bind.appValidators) in zk.xml.

<library-property>
    <name>org.zkoss.bind.appValidators</name>
    <value>foo=my.FooValidator,bar=my.BarValidator</value>
</library-property>

Then use them by validator's name.

<textbox value="@bind(vm.name) @validator('foo')"/>
<textbox value="@bind(vm.value) @validator('bar')"/>

[1]: An application level validator only has one instance and is shared between all binders.

Using a Built-in Validator

ZK provides some built-in validators (see All Known Implementing Classes at Validator that you can use directly with syntax

@validator('validatorName')

The validatorName should be replaced with the built-in validator's name (listed below):

  • beanValidator
  • formBeanValidator

Bean Validator

This validator integrates the Java Bean Validation[1] framework that defines a metadata model and API [2] to validate JavaBeans. Developers can specify the constraints of a bean's property by Java annotations and validate against the bean by API. By using ZK's bean validator, you only have to specify constraints on bean's properties. The bean validator will then invoke the API to validate for you.

[1]: JSR 303: http://jcp.org/en/jsr/detail?id=303

[2]: Bean validator API: http://jackson.codehaus.org/javadoc/bean-validatio-api/1.0/index.html

Preparing to Use JSR 303

Required Jars

A known implementation of JSR 303 is Hibernate Validator. The following is a sample dependency list in pom.xml for using Hibernate Validator:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.0.2.GA</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.6.1</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.1</version>
</dependency>

Hibernate Validator assumes the use of log4j to log information. You can use any log4j implementation you prefer.

Setup

Under project classpath, you need to prepare:

  1. log4j.properties
  2. META-INF/validation.xml

Here are examples of minimal settings in these files:

log4j.properties

log4j.rootLogger=info, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

META-INF/validation.xml

<?xml version="1.0" encoding="UTF-8"?>
<validation-config
    xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration">
    <default-provider>org.hibernate.validator.HibernateValidator</default-provider>
    <message-interpolator>org.hibernate.validator.engine.ResourceBundleMessageInterpolator</message-interpolator>
    <traversable-resolver>org.hibernate.validator.engine.resolver.DefaultTraversableResolver</traversable-resolver>
    <constraint-validator-factory>org.hibernate.validator.engine.ConstraintValidatorFactoryImpl</constraint-validator-factory>
</validation-config>

You can customize the settings according to your requirements.

Usage

Add constraint in JavaBean's property with Java annotation.

public static class User {
    private String _lastName = "Chen";

    @NotEmpty(message = "Last name can not be null")
    public String getLastName() {
        return _lastName;
    }

    public void setLastName(String name) {
        _lastName = name;
    }
}

Use this validator with its name beanValidator and with syntax:

@validator('beanValidator')

<window id="win"
    viewModel="@id('vm') @init(foo.MyViewModel)"
    validationMessages="@id('vmsgs')">
    <textbox id="tb" value="@bind(vm.user.lastName) @validator('beanValidator')"/>
    <label value="@load(vmsgs[tb])"/>
</window>

Since ZK 8.5.0 it also supports assigning a self-defined validation message key with syntax:

@validator('beanValidator', key='someKey')

<window id="win"
    viewModel="@id('vm') @init(foo.MyViewModel)"
    validationMessages="@id('vmsgs')">
    <textbox value="@bind(vm.user.lastName) @validator('beanValidator', key='lastName')"/>
    <label value="@load(vmsgs['lastName'])"/>
</window>

Validate Form's Property Loaded from a Bean

Available for ZK: EE

Since 6.0.1

It also supports validating a form object's property loaded from a bean[3].

<grid form="@id('fx') @load(vm.user) @save(vm.user, after='save')">
    <textbox id="tb" value="@bind(fx.lastName) @validator('beanValidator')"/>
    <label value="@load(vmsgs[tb])"/>
</grid>

[3]: It validates the last loaded bean of the form, which means it doesn't support validating a form that does not need to be loaded by a bean yet.

Form Bean Validator

Available for ZK: EE

Since 6.0.1

Like Bean Validator, this validator also integrates JavaBean Validation and validates a bean's all-saving properties. For the configuration and JavaBean usage, please refer to #Prepare_to_Use_JSR_303

Since ZK 8.0.0 the form binding is based on Java Proxies, since only methods are intercepted you have to place the constraint annotations at property level (getter methods - and not at field level).

Usage

Use this validator with the name formBeanValidator and set a unique[1] prefix key by prefix argument of validator. When any property of the bean is invalid, it puts the invalid message to into the validation message holder with key prefix+propertyName.

[1]: The prefix has to be unique in the same binder.

Syntax:

@validator('formBeanValidator', prefix='p_')

<window id="win" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init(foo.MyViewModel)"
    validationMessages="@id('vmsgs')">
    <grid width="600px" form="@id('fx') @load(vm.user) @save(vm.user, after='save')
        @validator('formBeanValidator', prefix='p_')">
        <textbox value="@bind(fx.firstName)"/>
        <label value="@load(vmsgs['p_firstName'])"/>
    </grid>
<!--more components-->
</window>

Limitation

This validator cannot validate a form object's nested property like fx.firstProp.secondProp. It can only validate a property like fx.firstProp. Nested bean validation is supported by using @Valid, and the validation messages will be accessible in the form fx.firstProp.secondProp.

Groups

Available for ZK: EE

Since 8.0.0

The concept of constraints grouping means that we can easily divide contraints into groups. It means that we can create partial validation for different purpose.

Usage

There are four main parts in a validation group:

  • Group Interface
  • Java Beans
  • @Validator in zul file
  • ViewModel

First we need to define an empty interface:

public interface GroupValidation {}

And there are two JavaBeans : PersonDto , NameDto

public class PersonDto {
    private NameDto nameDto;
    private int age;
    private int age2;

    @Max(value=120, groups=GroupValidation.class)
    public int getAge() {
        return age;
    }

    @Max(value=100)
    public int getAge2() {
        return age2;
    }

    @Valid //will trigger recursive validation
    @NotNull
    public NameDto getNameDto() {
        return nameDto;
    }

    // other getter/setters
}
public class NameDto {
    private String name;

    @Size(min=4, max=10, groups=GroupValidation.class)
    public String getName() {
        return name;
    }
    // setter
}

In these two JavaBeans, we can see the constraint of properties and the group in Java annotation.

Then we should specify the group in zul and viewmodel.

Syntax:

@validator('formBeanValidator', prefix='p_', groups=vm.validationGroups)"

In zul:

<div form="@id('fx') @load(vm.personDto) @save(vm.personDto, after='submit')
    @validator('formBeanValidator', prefix='p_', groups=vm.validationGroups)">

    <intbox value="@bind(fx.age)"/>
    <label id="err1" value="@load(vmsgs['p_age'])"/>
    <textbox value="@bind(fx.nameDto.name)"/>
    <label id="err2" value="@load(vmsgs['p_nameDto.name'])"/>
    <intbox value="@bind(fx.age2)"/>
    <label id="err3" value="@load(vmsgs['p_age2'])"/>
</div>

In viewmodel:

public class FromValidationViewModel {
    private PersonDto personDto = new PersonDto();

    public PersonDto getPersonDto() {
        return personDto;
    }

    public Class[] getValidationGroups() {
        return new Class[]{GroupValidation.class};
    }

    @Command("submit")
    public void submit() {
        //submit
    }
}

In this case, only the validations of the name and age would be triggered. The validation of age2 would be ignored.