> Markdown version of [Validating User Input](https://vaadin.com/docs/latest/hilla/lit/guides/forms/binder-validation). Section index: [llms.txt](https://vaadin.com/docs/latest/hilla/llms.txt)

# Validating User Input

Hilla helps you validate user input based on the backend Java data model. It reads the Bean Validation ([`JSR-380`](https://beanvalidation.org/2.0-jsr380/spec)) annotations on your Java data types and applies these constraints to the user input. Validation is enabled by default in all forms created with the Hilla `Binder` API.

When creating forms in TypeScript with Lit and the `Binder` API, all data model constraints are automatically applied to your form fields. The `Binder` API validates most standard constraints, such as `@Max`, `@Size`, `@Pattern`, `@Email` on the client side, without a network round-trip delay (see the full list in the [Built-In Client-Side Validators](#built-in-client-side-validators) section later). When you eventually submit data to server-side services, Hilla validates all constraints on the server as well. The `Binder` API updates the form to show any server-side validation errors.

## <a id="specifying-constraints"></a>Specifying Constraints

Constraints are specified as a part of the data model, in the Java code. You can use any of the [built-in constraints](https://beanvalidation.org/2.0/spec/#builtinconstraints) in addition to your own.

This example defines constraints on individual object properties using Bean Validation annotations:

```java
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public class Employee {
    @NotBlank
    private String username;

    private String title;

    @Email(message = "Please enter a valid e-mail address")
    private String email;

    // + other fields, constructors, setters and getters
}
```

During the build, when Hilla generates TypeScript types, it includes the constraint information in the generated model types. All forms that work with the same entity type have the same set of constraints.

When you create a form for an entity type, it gets the user input validation automatically.

Binding form fields to the data model

```typescript
import { Binder, field } from '@vaadin/hilla-lit-form';
import EmployeeModel from 'Frontend/generated/com/example/application/EmployeeModel';

...

private binder = new Binder(this, EmployeeModel);

render() {
  const { model } = this.binder;

  return html`
    <vaadin-text-field
      label="Username"
      ${field(model.username)}
    ></vaadin-text-field>
    <vaadin-text-field
      label="Title"
      ${field(model.title)}
    ></vaadin-text-field>
    <vaadin-email-field
      label="Email"
      ${field(model.email)}
    ></vaadin-email-field>
  `;
}
```

[Image: A form showing three fields where some of the fields are showing a validation error message]

Validation Errors

## <a id="defining-custom-constraints"></a>Defining Custom Constraints

The Bean Validation standard allows the creation of arbitrary [custom constraints](https://beanvalidation.org/2.0-jsr380/spec/#constraintsdefinitionimplementation-constraintdefinition-examples). The Hilla form `Binder` API also supports such custom constraints. The example that follows shows how to create and use a custom `@StrongPassword` constraint:

Defining Custom `@StrongPassword` Constraint

```java
@Retention(RUNTIME)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Constraint(validatedBy = { StrongPasswordValidator.class })
public @interface StrongPassword {

    // min required password strength on the scale from 1 to 5
    int minStrength() default 4;

    String message() default "Please enter a strong password";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}
```

This example defines a validator for the custom `@StrongPassword` constraint:

```java
public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {

    @Override
    public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
        // Use the zxcvbn library to measure the password strength
        Strength strength = zxcvbn.measure(object);

        // fail the validation if the measured strength is insufficient
        if (strength.getScore() < minStrength) {
            constraintContext
                .buildConstraintViolationWithTemplate(
                        strength.getFeedback().getWarning())
                .addConstraintViolation();
            return false;
        }

        return true;
    }
}
```

This example uses a third-party library to measure the password strength with a custom validator. Add a dependency to your `pom.xml`:

```xml
<dependency>
    <groupId>com.nulab-inc</groupId>
    <artifactId>zxcvbn</artifactId>
    <version>1.3.0</version>
</dependency>
```

This example uses the custom `@StrongPassword` constraint in a data type:

```java
public class Employee {

    @StrongPassword
    private String password;
}
```

No additional steps are needed to start using the new validation rules in forms. The `field()` directive applies all server-side constraints, automatically.

`profile-view.ts`

```typescript
private binder = new Binder(this, EmployeeModel);

render() {
  const { model } = this.binder;

  return html`
    <vaadin-password-field
      label="Password"
      ${field(model.password)}
    ></vaadin-password-field>

    <vaadin-button @click="${this.save}">Save</vaadin-button>
  `;
}
```

However, in this example, validation happens only after the form is submitted. To validate user input immediately, as the user types, you would need to define a validator in TypeScript, as well. The following section shows how to do this.

## <a id="defining-custom-client-side-validators"></a>Defining Custom Client-Side Validators

To give instant feedback to users as they type, you can define validators in TypeScript, so that they are executed in the browser, without a network round trip. The Hilla form `Binder` API allows you to add validators for both individual fields, and for the form as a whole (e.g., to implement cross-field validation). Client-side validators are executed before the server-side validation is invoked.

> **Warning:** Validation ALWAYS needs to run on the server in order for your application to be secure. Additionally, you can validate input in the browser immediately the user types, to give a better user experience.

### <a id="single-field-ts-validators"></a>Adding Validators for a Single Field

When a validation rule concerns a single field, a client-side validator should be added with the `addValidator()` call on the binder node for that particular field. This is the case with the custom `@StrongPassword` constraint example.

[Image: A form where a field is showing a customized validation error message]

Custom Field Validation Error

`profile-view.ts`

```typescript
import * as owasp from 'owasp-password-strength-test';

// binder.for() returns a binder for the password field
const model = this.binder.model;
this.binder.for(model.password).addValidator({
  message: 'Please enter a strong password',
  validate: (password: string) => {
    const result = owasp.test(password);
    if (result.strong) {
      return true;
    }
    return { property: model.password, message: result.errors[0] };
  },
});
```

This example uses a third-party library to measure the password strength with a custom validator. Add a dependency to your `package.json`:

```terminal
npm install --save owasp-password-strength-test
npm install --save-dev @types/owasp-password-strength-test
```

### <a id="adding-cross-field-validators"></a>Adding Cross-Field Validators

When a validation rule is based on several fields, a client-side validator should be added with the `addValidator()` call on the form binder directly. A typical example where this would be needed is checking that a password is repeated correctly:

[Image: A form where a field is showing a customized validation error message]

Custom Field Validation Error

```typescript
private binder = new Binder(this, EmployeeModel);

render() {
  return html`
    <vaadin-password-field
      label="Password"
      ${field(model.password)}
    ></vaadin-password-field>
    <vaadin-password-field
      label="Repeat password"
      ${field(model.repeatPassword)}
    ></vaadin-password-field>
  `;
}

protected firstUpdated() {

  const model = this.binder.model;
  this.binder.addValidator({
    message: 'Please check that the password is repeated correctly',
    validate: (value: Employee) => {
      if (value.password != value.repeatPassword) {
        return [{ property: model.password }];
      }
      return [];
    }
  });
}
```

When record-level validation fails, there are cases when you want to mark several fields as invalid. To do this with the `@vaadin/hilla-lit-form` validator APIs, you can return an *array* of `{ property, message }` records from the `validate()` callback. Returning an empty array is equivalent to returning `true`, meaning that validation has passed. If you need to indicate a validation failure without marking any particular field as invalid, return `false`.

## <a id="marking-fields-as-required"></a>Marking Fields as Required

To mark a form field as 'required', you can add a `@NotNull` or `@NotEmpty` constraint to the corresponding property in the Java type. `@Size` with a `min` value greater than 0 also causes a field to be required.

Alternatively, you can set the `impliesRequired` property when adding a custom validator in TypeScript, as shown earlier, in the [xref:binder-validation](https://vaadin.com/docs/latest/hilla/lit/guides/forms/binder-validation.md#single-field-ts-validators) section.

The fields marked as *required* have their `required` property set by the `field()` directive. Hence, validation fails if they are left empty.

## <a id="built-in-client-side-validators"></a>Built-In Client-Side Validators

The `@vaadin/hilla-lit-form` package provides the client-side validators for the following [JSR-380 built-in constraints](https://beanvalidation.org/2.0/spec/#builtinconstraints):

1. `Email` – The string must be a well-formed email address

2. `Null` – Must be `null`

3. `NotNull` – Must not be `null`

4. `NotEmpty` – Must not be `null` nor empty (must have a `length` property, for example string or array)

5. `NotBlank` – Must not be `null` and must contain at least one non-whitespace character

6. `AssertTrue` – Must be `true`

7. `AssertFalse` – Must be `false`

8. `Min` – Must be a number greater than or equal to the specified minimum

   - Additional options: `{ value: number | string }`

9. `Max` - Must be a number less than or equal to the specified maximum

   - Additional options: `{ value: number | string }`

10. `DecimalMin` – Must be a number greater than or equal to the specified minimum

    - Additional options: `{ value: number | string, inclusive: boolean | undefined }`

11. `DecimalMax` – Must be a number less than or equal to the specified maximum

    - Additional options: `{ value: number | string, inclusive: boolean | undefined }`

12. `Negative` – Must be a negative number (0 is considered to be an invalid value)

13. `NegativeOrZero` – Must be a negative number or 0

14. `Positive` – Must be a positive number (0 is considered to be an invalid value)

15. `PositiveOrZero` – Must be a positive number or 0

16. `Size` – Size must be in the specified range, inclusive; must have a `length` property, for example a string or an array

    - Additional options: `{ min?: number, max?: number }`

17. `Digits` – Must be a number within the specified range

    - Additional options: `{ integer: number, fraction: number }`

18. `Past` – A date string in the past

19. `PastOrPresent` – A date string in the past or present

20. `Future` – A date string in the future

21. `FutureOrPresent` – A date string in the future or present

22. `Pattern` – Must match the specified regular expression

    - Additional options: `{ regexp: RegExp | string }`

These are usually used automatically. However, you could also add them to selected fields manually with `binder.for(myFieldModel).addValidator(validator)`; for example, `addValidator(new Size({max: 10, message: 'Must be 10 characters or less'}))`.

All the built-in validators take one constructor parameter, which is usually an optional `options` object with a `message?: string` property (which defaults to `'invalid'`). However, some validators have additional options or support other argument types, instead of the `options` object.

For example, the `Min` validator requires a `value: number | string` option. This can be given as part of the `options` object. Alternatively, you can pass the minimum value itself, instead of the `options` object (if you don’t want to set `message` and leave it as the default `'invalid'`).

```typescript
import { Binder, field, NotEmpty, Min, Size, Email } from '@vaadin/hilla-lit-form';

@customElement('my-demo-view')
export class MyDemoView extends LitElement {
  private binder = new Binder(this, PersonModel);

  protected firstUpdated(_changedProperties: any) {
    super.firstUpdated(args);

    const model = this.binder.model;

    this.binder.for(model.name).addValidator(
      new NotEmpty({
        message: 'Please enter a name'
      }));

    this.binder.for(model.username).addValidator(
      new Size({
        message: 'Please pick a username 3 to 15 symbols long',
        min: 3,
        max: 15
      }));

    this.binder.for(model.age).addValidator(
      new Min({
        message: 'Please enter an age of 18 or above',
        value: 18
      }));

    this.binder.for(model.email).addValidator(new Email());
  }

  render() {
    const model = this.binder.model;
    return html`
      <vaadin-text-field label="Name"
        ${field(model.name)}"></vaadin-text-field>
      <vaadin-text-field label="Username"
        ${field(model.username)}"></vaadin-text-field>
      <vaadin-integer-field label="Age"
        ${field(model.age)}"></vaadin-integer-field>
      <vaadin-email-field label="Email"
        ${field(model.email)}"></vaadin-email-field>
    `;
  }
}
```

## <a id="validation-message-interpolation"></a>Validation Message Interpolation

You can use the low-level `interpolateMessageCallback()` API to customize the validation messages on the client side before they are displayed to the user. This can be used for localization purposes.

`Binder` has an optional static property `interpolateMessageCallback` which is shared by all binder instances. It can be set to a callback function that returns the validation message you want to display to the user. The `interpolateMessageCallback()` is called every time a validator returns a message as a result of a validation being run. It receives the original validation message string, the `Validator` instance, as well as the related `BinderNode`, as context which can be used to decide what message you want to return. The `Validator` instance holds the name of the validator, which can be used to look up a translation for the message. The `name` property is not empty for built-in validators, and it can be set for custom validators as well.

### <a id="callback-parameters"></a>Callback Parameters

`interpolateMessageCallback()` receives the following parameters and returns a `string`.

| Parameter    | Type                                  | Description                                                                                                                                                                                                                             |
| ------------ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `message`    | `string`                              | The original validation message returned by the `Validator`. This may be a default validation message from a built-in validator, custom validator or a custom message defined on a Java Bean Validation annotation of a specific field. |
| `validator`  | `Validator<any>`                      | The `Validator` instance that returned the message.                                                                                                                                                                                     |
| `binderNode` | `BinderNode<any, AbstractModel<any>>` | The `BinderNode` instance for which the validation was run. You can get the related model, value or `Binder` instance from the binder node.                                                                                             |

### <a id="message-interpolation-example"></a>Message Interpolation Example

This example shows how to use the `lit-translate` package together with `interpolateMessageCallback()` to translate validation error messages. Make sure to install the `lit-translate` `npm` package first.

`index.ts`

```typescript
// ... other imports
import { get, registerTranslateConfig, use } from 'lit-translate';

// Configure lit-translate
const translateConfig = registerTranslateConfig({
  loader: lang => fetch(`/i18n/${lang}.json`).then(res => res.json()),
});
use('fi');

Binder.interpolateMessageCallback = (message, validator, binderNode) => {
  // Try to find a translation for the specific type of validator by its name:
  if (validator.name !== undefined) {
    let key = `validationError.${validator.name}`;

    // Special case for DecimalMin and DecimalMax validators to use different message based on "inclusive" property
    if (['validationError.DecimalMin', 'validationError.DecimalMax'].includes(key)) {
      key += (validator as any).inclusive ? '.inclusive' : '.exclusive';
    }

    if (translateConfig.lookup(key, translateConfig)) {
      return get(key, validator as any);
    }
  }

  // Fall back to original message if no translations are found
  return message;
};

// ... Router configuration
```

Sample translations for all error messages of built-in validators.

`src/main/resources/META-INF/resources/i18n/fi.json`

```json
{
  "validationError": {
    "AssertFalse": "täytyy olla epätosi",
    "AssertTrue": "täytyy olla tosi",
    "DecimalMax": {
      "inclusive": "täytyy olla pienempi tai yhtä suuri kuin {{ value }}",
      "exclusive": "täytyy olla pienempi kuin {{ value }}"
    },
    "DecimalMin": {
      "inclusive": "täytyy olla suurempi tai yhtä suuri kuin {{ value }}",
      "exclusive": "täytyy olla suurempi kuin {{ value }}"
    },
    "Digits": "numero ei täsmää rajoituksiin (<{{ integer }} numeroa>.<{{ fraction }} numeroa> odotettu)",
    "Email": "täytyy olla kelvollinen sähköpostiosoite",
    "Future": "täytyy olla tuleva päivämäärä",
    "Max": "täytyy olla pienempi tai yhtä suuri kuin {{ value }}",
    "Min": "täytyy olla suurempi tai yhtä suuri kuin {{ value }}",
    "Negative": "täytyy olla pienempi kuin 0",
    "NegativeOrZero": "täytyy olla pienempi tai yhtä suuri kuin 0",
    "NotBlank": "ei saa olla tyhjä",
    "NotEmpty": "ei saa olla tyhjä",
    "NotNull": "ei saa olla null",
    "Null": "täytyy olla null",
    "Past": "täytyy olla menneisyyden päivämäärä",
    "Pattern": "täytyy täsmätä seuraavaan säännölliseen lausekkeeseen (regexp): {{ regexp }}",
    "Positive": "täytyy olla suurempi kuin 0",
    "PositiveOrZero": "täytyy olla suurempi tai yhtä suuri kuin 0",
    "Size": "pituuden täytyy olla {{ min }} ja {{ max }} väliltä"
  }
}
```
