> Markdown version of [Form Validation](https://vaadin.com/docs/next/building-apps/forms-data/add-form/validation). Section index: [llms.txt](https://vaadin.com/docs/next/building-apps/llms.txt)

# Form Validation

Form validation is a fundamental aspect of building robust and user-friendly applications. It ensures that the data entered by users meets the expected format and business rules before it’s processed or stored. Effective validation helps safeguard the **security** and **integrity** of your application. It also enhances the **user experience** by providing immediate feedback and preventing common input errors.

To learn more about validation from a broader architectural perspective, see the [Data Consistency](https://vaadin.com/docs/next/building-apps/forms-data/consistency.md) deep dive.

> **Tip: Part of a Series**
>
> This is part 2 of the **Add a Form** series. It builds on the concepts introduced in the [Fields & Binding](https://vaadin.com/docs/next/building-apps/forms-data/add-form/fields-and-binding.md) guide.

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

To mark a field as required, use the `asRequired()` method. This ensures that a user cannot leave the field empty when submitting the form.

```java
binder.forField(title)
// tag::snippet[]
      .asRequired() // (1)
// end::snippet[]
      .bind(Proposal::getTitle, Proposal::setTitle);
binder.forField(proposalType)
// tag::snippet[]
      .asRequired("Please select a proposal type") // (2)
// end::snippet[]
      .bind(Proposal::getType, Proposal::setType);
```

1. Marks the field as required but does not display an error message when left blank. The field is visually indicated as required.

2. Displays the specified error message when the user leaves the field blank.

Using `asRequired()` has two effects:

1. A visual required indicator appears on the field.

2. The validation checks whether the field’s value matches its `emptyValue` — for example, an empty string for `TextField`, or `null` for components like `DatePicker` and `ComboBox`.

## <a id="binding-level-validators"></a>Binding-Level Validators

`Binding`-level validators **check individual field values** against specific conditions before allowing them to be copied to the FDO (Form Data Object). To add a validator, use the `withValidator()` method:

```java
binder.forField(title)
      .asRequired() // (1)
// tag::snippet[]
      .withValidator(new StringLengthValidator(
          "Title must be between 1 and 100 characters", 1, 100)) // (2)
// end::snippet[]
       .bind(Proposal::getTitle, Proposal::setTitle);
```

1. Ensures the field is not empty.

2. Enforces a character limit.

While `asRequired()` prevents empty fields, other validators enforce constraints like value ranges, valid formats, or business-specific rules.

### <a id="built-in-validators"></a>Built-in Validators

Vaadin provides a set of **built-in validators** for common validation scenarios:

- **Numeric Range Validators** — Ensure that a numeric value falls within a valid range.

  - `BigDecimalRangeValidator`, `BigIntegerRangeValidator`, `ByteRangeValidator`, `DoubleRangeValidator`, `FloatRangeValidator`, `IntegerRangeValidator`, `LongRangeValidator`, `ShortRangeValidator`

- **Date Validators** — Ensure that a date or time value falls within a valid range.

  - `DateRangeValidator`, `DateTimeRangeValidator`

- **Other Validators**

  - `EmailValidator` — Ensures the value is a valid email address.

  - `RangeValidator` — Works for any type using a `Comparator`.

  - `RegexpValidator` — Ensures the value matches a specified regular expression.

  - `StringLengthValidator` — Ensures a string is within a valid length range.

Use these built-in validators when possible to avoid unnecessary custom implementations.

> **Note: Jakarta Bean Validation**
>
> If you are using Jakarta Bean Validation, Vaadin can delegate to it for validation. For details, see the [Binding Beans to Forms](https://vaadin.com/docs/next/flow/binding-data/components-binder-beans.md#using-jsr-303-bean-validation) reference guide.

### <a id="custom-validators"></a>Custom Validators

If the built-in validators do not meet your requirements, you can create a custom validator by implementing `Validator<T>`. The following example ensures an integer is positive:

PositiveIntegerValidator.java

```java
public class PositiveIntegerValidator implements Validator<Integer> {

    @Override
    public ValidationResult apply(Integer num, ValueContext context) { // (1)
        return (num == null || num > 0) // (2)
            ? ValidationResult.ok()
            : ValidationResult.error("number must be positive");
    }
}
```

1. The `ValueContext` gives you access to information like the current locale, the field component, the `Binder`, etc.

2. An empty field has a `null` value. The validator accepts it, and leaves required fields to `asRequired()`.

You can also implement a validator using a lambda expression instead of a separate class:

```java
binder.forField(myNumberField)
      .withValidator(num -> num == null || num > 0, "number must be positive")
      .bind(myBean::getInteger, myBean::setInteger);
```

### <a id="converters"></a>Converters

If you’re using value objects or domain primitives in your FDO, you can create a converter by implementing `Converter<T>`. The following example converts between an `EmailAddress` and a `String`:

EmailAddressConverter.java

```java
public class EmailAddressConverter implements Converter<String, EmailAddress> {

    @Override
    public Result<EmailAddress> convertToModel(String value, ValueContext context) {
        if (value == null) {
            return Result.ok(null);
        }
        try {
            return Result.ok(new EmailAddress(value));
        } catch (IllegalArgumentException e) {
            return Result.error(e.getMessage());
        }
    }

    @Override
    public String convertToPresentation(EmailAddress emailAddress,
                                        ValueContext context) {
        return emailAddress == null ? null : emailAddress.toString();
    }
}
```

You’d use it with `Binder` like this:

```java
binder.forField(myEmailAddress)
      .withConverter(new EmailAddressConverter())
      .bind(myBean::getEmail, myBean::setEmail);
```

Converters implicitly perform validation. For instance, if the `EmailAddress` constructor throws an exception, `Binder` shows the error message as a validation message.

You can add validators after the converter as well. For example, if you need to validate that an email address has not been used already, you could do something like this:

```java
binder.forField(myEmailAddress)
      .withConverter(new EmailAddressConverter())
      .withValidator(emailValidationService::notAlreadyInUse,
          "The email address is already in use")
      .bind(myBean::getEmail, myBean::setEmail);
```

For more information about domain primitives, see the [Domain Primitives](https://vaadin.com/docs/next/building-apps/forms-data/consistency/domain-primitives.md) deep dive.

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

Sometimes, whether a value is valid depends on the value of another field. For example, an end date can’t be before the start date. By default, a `Binding`-level validator runs only when the value of its own field changes, so it wouldn’t notice a change to the start date.

To make the validator react to the other field, too, read the other field’s value through the value signal of its binding (since V25.1). `Binder` then re-runs the validator whenever that value changes:

```java
var startDateBinding = binder.forField(startDate)
      .bind(MyBean::getStartDate, MyBean::setStartDate);

binder.forField(endDate)
      .withValidator(end -> {
          var start = startDateBinding.valueSignal().get(); // (1)
          return start == null || end == null || !end.isBefore(start);
      }, "End date cannot be before start date")
      .bind(MyBean::getEndDate, MyBean::setEndDate);
```

1. Reading the value signal makes `Binder` re-run this validator whenever the start date changes.

`Binder` now checks the rule whenever either date changes, and shows the error next to the end date field. You don’t need a second validator on the start date field for the same rule.

If the error belongs to the form as a whole rather than to a single field, use a [Binder-level validator](#binder-level-validators) instead. For more about cross-field validation, see [Triggering Revalidation](https://vaadin.com/docs/next/flow/binding-data/components-binder-validation.md#triggering-revalidation) and [Form Binding with Dynamic Validation](https://vaadin.com/docs/next/flow/ui-state/usage-examples/binder-integration.md) in the reference guide.

## <a id="default-validators"></a>Default Validators

Some fields include **default validators** that enforce constraints directly within the component. These validators improve UX by preventing invalid input before submission.

For example, `DatePicker` has built-in min and max constraints. If a user selects a date outside the range, the field automatically becomes invalid.

```java
myDatePicker.setMin(LocalDate.now());
```

To disable all default validators in a `Binder`:

```java
binder.setDefaultValidatorsEnabled(false);
```

To disable validation for a specific field:

```java
binder.forField(myDatePicker)
      .withDefaultValidator(false)
      .bind(MyBean::getDate, MyBean::setDate);
```

## <a id="binder-level-validators"></a>Binder-Level Validators

Unlike `Binding`-level validators, which validate individual fields, `Binder`-level validators **validate the entire FDO** after all fields have been processed.

The following example ensures that the start date is not after the end date:

```java
binder.withValidator((bean, valueContext) -> {
    if (bean.getStartDate() != null && bean.getEndDate() != null
            && bean.getStartDate().isAfter(bean.getEndDate())) {
        return ValidationResult.error("Start date cannot be after end date");
    }
    return ValidationResult.ok();
});
```

## <a id="triggering-validation"></a>Triggering Validation

Validation can be **triggered automatically** or **programmatically**.

`Binding`-level validators are always triggered whenever a field value changes.

`Binder`-level validators are triggered differently depending on whether the form is operating in **buffered mode** or **write-through mode**:

- **Buffered mode**: Validators are only triggered when calling `writeBean()` or `writeRecord()`.

- **Write-through mode**: Validators are triggered whenever a field value changes.

> **Note:** When validating the FDO, `Binder` first writes the change to the FDO, then runs the validators. If any validator fails, `Binder` reverts the change. Any extra business logic in the setters of the FDO must consider this.

You can also trigger validation without writing to the FDO:

- `isValid()` — Checks all validators but does not update the UI.

- `validate()` — Checks all validators and updates the UI if needed.

> **Important:** If you have `Binder`-level validators, these methods only work in **write-through mode**.

## <a id="handling-validation-errors"></a>Handling Validation Errors

By default, `Binding`-level validation errors are displayed next to the corresponding input fields.

For `Binder`-level validation errors, which do not belong to a specific field, you can use a **status label** to display error messages:

```java
var beanValidationErrors = new Div();
beanValidationErrors.addClassName("form-errors");

binder.setStatusLabel(beanValidationErrors);
```

Then give the status label an error color in your stylesheet:

```css
.form-errors {
    color: var(--aura-red-text);
}
```

The color comes from Aura, the default theme. With Lumo, use `var(--lumo-error-text-color)` instead.

This ensures that validation messages are displayed appropriately, whenever they originate from `Binding`-level validation or `Binder`-level validation.
