Skip to content

avaje/avaje-validator

Folders and files

NameName
Last commit message
Last commit date
Feb 18, 2025
Mar 31, 2025
Jan 1, 2024
Mar 15, 2025
Mar 31, 2025
Mar 31, 2025
Mar 15, 2025
Mar 24, 2025
Mar 15, 2025
May 4, 2023
Jul 1, 2024
Apr 14, 2023
Jan 27, 2025
Mar 31, 2025

Repository files navigation

Supported JVM Versions Discord Build native image build Maven Central javadoc License

Reflection-free pojo validation via apt source code generation. A light (~120kb + generated code) source code generation style alternative to Hibernate Validation. (code generation vs reflection)

  • Annotate java classes with @Valid (or use @ImportValidPojo for types we "don't own" such as external dependencies)
  • avaje-validator-generator annotation processor generates Java source code to write validation classes
  • Supports Avaje/Jakarta/Javax Constraint Annotations
  • Groups Support
  • Class level Constraints
  • Composable Contraint Annotations
  • Inheritable Constraints
  • loading and interpolating error messages (with multiple Locales) through ResourceBundles
  • Getter Validation
  • Method parameter validation (requires a DI container to retrieve the generated MethodAdapter classes)

Documentation

Goto https://avaje.io/validator/ for full documentation.

Requires Java 17+

Avaje Validator requires Java 17 or higher.

Quick Start

Step 1 - Add dependencies

<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-validator</artifactId>
  <version>${avaje.validator.version}</version>
</dependency>
<!-- Alternatively can use Jakarta/Javax Constraints-->
<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-validator-constraints</artifactId>
  <version>${avaje.validator.version}</version>
</dependency>

And add avaje-validator-generator as an annotation processor.

<!-- Annotation processors -->
<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-validator-generator</artifactId>
  <version>${avaje.validator.version}</version>
  <optional>true</optional>
  <scope>provided</scope>
</dependency>

Step 2 - Add (Avaje/Jakarta/Javax) @Valid

Add @Valid to the types we want to add validation.

The avaje-validator-generator annotation processor will generate validation adapter classes as Java source code for each type annotated with @Valid. These will be automatically registered with Validator when it is started using a service loader mechanism.

@Valid
public class Address {
  @NotBlank
  private String street;

  @NotEmpty(message="must not be empty")
  private List<@NotBlank(message="{message.bundle.key}") String> suburb;

  @Valid
  @NotNull(groups=SomeGroup.class)
  private OtherClass otherclass;

  //add getters/setters
}

It also works with records:

@Valid
public record Address(
      @NotBlank String street,
      @NotEmpty(message="must not be empty") String suburb,
      @NotNull(groups=SomeGroup.class) String city
      ) {}

For types we cannot annotate with @Valid we can place @ImportValidPojo on any class/package-info to generate the adapters.

Step 3 - Use

// build using defaults
Validator validator = Validator.builder().build();

Customer customer = ...;

// will throw a `ConstraintViolationException` containing all the failed constraint violations
validator.validate(customer);

// validate with explicit locale
validator.validate(customer, Locale.ENGLISH);

// validate with groups
validator.validate(customer, Locale.ENGLISH, Group1.class);

Generated Code

Given the class:

@Valid
public record Address(
      @NotBlank String street,
      @NotEmpty(message="must not be empty") String suburb,
      @NotNull(groups=SomeGroup.class) String city
      ) {}

The following code will be generated and used for validation.

@Generated
public final class AddressValidationAdapter implements ValidationAdapter<Address> {

  private final ValidationAdapter<String> streetValidationAdapter;
  private final ValidationAdapter<String> suburbValidationAdapter;
  private final ValidationAdapter<String> cityValidationAdapter;

  public AddressValidationAdapter(ValidationContext ctx) {
    this.streetValidationAdapter =
        ctx.<String>adapter(NotBlank.class, Map.of("message","{avaje.NotBlank.message}"));
    this.suburbValidationAdapter =
        ctx.<String>adapter(NotEmpty.class, Map.of("message","must not be empty"));
    this.cityValidationAdapter =
        ctx.<String>adapter(NotNull.class, Map.of("message","{avaje.NotNull.message}", "groups",Set.of(example.avaje.typeuse.SomeGroup.class)));
  }

  @Override
  public boolean validate(Address value, ValidationRequest request, String propertyName) {
    if (propertyName != null) {
      request.pushPath(propertyName);
    }
    var _$street = value.street();
    streetValidationAdapter.validate(_$street, request, "street");

    var _$suburb = value.suburb();
    suburbValidationAdapter.validate(_$suburb, request, "suburb");

    var _$city = value.city();
    cityValidationAdapter.validate(_$city, request, "city");

    if (propertyName != null) {
      request.popPath();
    }
    return true;
  }
}