or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

annotation-config.mdaot.mdcaching.mdcontext-lifecycle.mdevents.mdformatting.mdi18n.mdindex.mdjmx.mdresilience.mdscheduling.mdstereotypes.mdvalidation.md
tile.json

validation.mddocs/

Validation

Bean Validation (JSR-380)

// Add dependency
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

// Domain model
public class User {

    @NotNull
    @Size(min = 2, max = 50)
    private String username;

    @Email
    private String email;

    @Min(18)
    @Max(120)
    private Integer age;

    @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$")
    private String phone;

    @Past
    private LocalDate birthDate;

    @Valid  // Nested validation
    private Address address;
}

Spring Validator Interface

@Component
public class UserValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return User.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        User user = (User) target;

        if (user.getUsername() == null || user.getUsername().isBlank()) {
            errors.rejectValue("username", "username.required", "Username is required");
        }

        if (user.getEmail() != null && !user.getEmail().contains("@")) {
            errors.rejectValue("email", "email.invalid", "Invalid email format");
        }

        // Cross-field validation
        if (user.getPassword() != null && !user.getPassword().equals(user.getConfirmPassword())) {
            errors.reject("password.mismatch", "Passwords do not match");
        }
    }
}

@Validated and Method Validation

@Service
@Validated
public class UserService {

    @Autowired
    private UserRepository repository;

    // Parameter validation
    public User createUser(@Valid User user) {
        return repository.save(user);
    }

    // Method parameter validation
    public User getUser(@NotNull @Min(1) Long id) {
        return repository.findById(id);
    }

    // Return value validation
    @Valid
    public User updateUser(Long id, @Valid User user) {
        return repository.save(user);
    }

    // Validation groups
    public void processUser(@Validated(BasicValidation.class) User user) {
        // Only validates BasicValidation group
    }
}

// Exception handling
@ControllerAdvice
public class ValidationExceptionHandler {

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<?> handleConstraintViolation(ConstraintViolationException ex) {
        Map<String, String> errors = ex.getConstraintViolations().stream()
            .collect(Collectors.toMap(
                v -> v.getPropertyPath().toString(),
                ConstraintViolation::getMessage
            ));
        return ResponseEntity.badRequest().body(errors);
    }
}

Validation Groups

public interface BasicValidation {}
public interface AdvancedValidation {}

public class User {

    @NotNull(groups = BasicValidation.class)
    @Size(min = 2, max = 50, groups = BasicValidation.class)
    private String username;

    @NotNull(groups = AdvancedValidation.class)
    @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$",
             groups = AdvancedValidation.class)
    private String password;

    @AssertTrue(groups = AdvancedValidation.class)
    public boolean isAgeVerified() {
        return age != null && age >= 18;
    }
}

// Usage
@Service
public class UserService {

    public void quickRegistration(@Validated(BasicValidation.class) User user) {}

    public void fullRegistration(@Validated({BasicValidation.class, AdvancedValidation.class}) User user) {}
}

Custom Constraints

// Annotation
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueUsernameValidator.class)
public @interface UniqueUsername {
    String message() default "Username already exists";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// Validator
@Component
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {

    @Autowired
    private UserRepository userRepository;

    @Override
    public boolean isValid(String username, ConstraintValidatorContext context) {
        if (username == null) return true;
        return !userRepository.existsByUsername(username);
    }
}

// Usage
public class User {
    @UniqueUsername
    private String username;
}

Programmatic Validation

@Service
public class ValidationService {

    @Autowired
    private Validator validator;

    public void validateUser(User user) {
        Set<ConstraintViolation<User>> violations = validator.validate(user);

        if (!violations.isEmpty()) {
            violations.forEach(v ->
                System.out.println(v.getPropertyPath() + ": " + v.getMessage())
            );
        }
    }

    // Validate specific property
    public void validateUsername(User user) {
        Set<ConstraintViolation<User>> violations =
            validator.validateProperty(user, "username");
    }

    // Validate specific group
    public void validateBasic(User user) {
        Set<ConstraintViolation<User>> violations =
            validator.validate(user, BasicValidation.class);
    }
}

LocalValidator Configuration

@Configuration
public class ValidationConfig {

    @Bean
    public LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(messageSource);
        return validator;
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(validator);
        return processor;
    }
}

Common Constraints

public class ValidationExamples {

    @NotNull                               // Must not be null
    @NotEmpty                              // Not null and not empty (collections/strings)
    @NotBlank                              // Not null and has non-whitespace
    @Null                                  // Must be null

    @Size(min = 2, max = 100)             // Size constraints
    @Min(18)                               // Minimum numeric value
    @Max(100)                              // Maximum numeric value
    @DecimalMin("0.0")                     // Decimal minimum
    @DecimalMax("100.0")                   // Decimal maximum
    @Digits(integer = 3, fraction = 2)     // Numeric format

    @Past                                  // Date in the past
    @PastOrPresent                         // Past or present
    @Future                                // Date in the future
    @FutureOrPresent                       // Future or present

    @Email                                 // Valid email format
    @Pattern(regexp = "...")               // Regex pattern match

    @Positive                              // Positive number
    @PositiveOrZero                        // Positive or zero
    @Negative                              // Negative number
    @NegativeOrZero                        // Negative or zero

    @AssertTrue                            // Boolean must be true
    @AssertFalse                           // Boolean must be false

    @Valid                                 // Nested validation

    private String field;
}