Implementing Data Validation

1. Implementing Input Validation

LayerValidates
Edge (controller)Format, presence, type
ServiceCross-field, multi-aggregate
DomainInvariants in entity
DBConstraints (defense-in-depth)

Example: Trigger validation in controller

@PostMapping("/orders")
public OrderResponse create(@Valid @RequestBody CreateOrderRequest req) { ... }

2. Implementing Business Rule Validation

Example: Enforce in domain

public void approve(Approver a) {
    if (status != PENDING) throw new BusinessRuleException("only pending can be approved");
    if (a.equals(submittedBy)) throw new BusinessRuleException("submitter cannot approve");
    this.status = APPROVED;
}

3. Using Validation Annotations

AnnotationUse
@NotNull/@NotBlankPresence
@SizeLength
@Min/@Max/@DecimalMinRange
@PatternRegex
@EmailRFC email
@ValidCascade
@AssertTrue/@AssertFalseBoolean
@Past/@Future/@PastOrPresentTime

4. Implementing Custom Validators

Example: Custom annotation

@Constraint(validatedBy = StrongPasswordValidator.class)
@Target({FIELD}) @Retention(RUNTIME)
public @interface StrongPassword {
    String message() default "weak password";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {
    public boolean isValid(String v, ConstraintValidatorContext c) {
        return v != null && v.length() >= 12 && v.matches(".*\\d.*") && v.matches(".*[A-Z].*");
    }
}

5. Implementing Validation Error Messages

PracticeDetail
i18n key{order.lines.required}
Resource bundleValidationMessages_en.properties
Field pathInclude in response
Code + messageMachine-readable + human

6. Implementing Cross-Field Validation

Example: Class-level constraint

@DateRangeValid  // class-level
public record DateRange(LocalDate from, LocalDate to) {}

public class DateRangeValidator implements ConstraintValidator<DateRangeValid, DateRange> {
    public boolean isValid(DateRange v, ConstraintValidatorContext c) {
        return v.from() == null || v.to() == null || !v.from().isAfter(v.to());
    }
}

7. Implementing Validation Groups

UseDetail
Different rules per opCreate vs Update
Marker interfaceinterface OnCreate {}
Apply@Validated(OnCreate.class)

8. Implementing Input Sanitization

ThreatSanitizer
XSSOWASP Java HTML Sanitizer
SQL injectionParameterized queries (don't sanitize, use binds)
Path traversalCanonicalize + allow-list base dir
CSV injectionPrefix dangerous chars

9. Implementing Schema Validation

FormatTool
JSONJSON Schema (networknt/json-schema-validator)
XMLXSD
Avro/ProtobufSchema registry (Confluent, Buf)
OpenAPIRequest/response validation middleware

10. Understanding Fail-Fast vs Collect-All Validation

ModeWhen
Fail-fastInternal calls; quick error
Collect-allUI forms; show all errors at once
Bean Validation defaultCollect-all per object

11. Implementing Fluent Validation

Example: Fluent rules

Validator<User> v = Validator.<User>builder()
    .require(u -> u.name() != null, "name.required")
    .check(u -> u.age() >= 18, "age.adult")
    .check(u -> u.email().contains("@"), "email.invalid")
    .build();
ValidationResult r = v.validate(user);

12. Implementing Domain Model Validation

WhereWhat
ConstructorReject invalid construction
MutatorReject invalid transitions
Aggregate rootMulti-entity invariants
Note: Always-valid domain objects mean callers cannot construct an invalid state.