Implementing Data Validation
1. Implementing Input Validation
| Layer | Validates |
|---|---|
| Edge (controller) | Format, presence, type |
| Service | Cross-field, multi-aggregate |
| Domain | Invariants in entity |
| DB | Constraints (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
| Annotation | Use |
|---|---|
@NotNull/@NotBlank | Presence |
@Size | Length |
@Min/@Max/@DecimalMin | Range |
@Pattern | Regex |
@Email | RFC email |
@Valid | Cascade |
@AssertTrue/@AssertFalse | Boolean |
@Past/@Future/@PastOrPresent | Time |
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
| Practice | Detail |
|---|---|
| i18n key | {order.lines.required} |
| Resource bundle | ValidationMessages_en.properties |
| Field path | Include in response |
| Code + message | Machine-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
| Use | Detail |
|---|---|
| Different rules per op | Create vs Update |
| Marker interface | interface OnCreate {} |
| Apply | @Validated(OnCreate.class) |
8. Implementing Input Sanitization
| Threat | Sanitizer |
|---|---|
| XSS | OWASP Java HTML Sanitizer |
| SQL injection | Parameterized queries (don't sanitize, use binds) |
| Path traversal | Canonicalize + allow-list base dir |
| CSV injection | Prefix dangerous chars |
9. Implementing Schema Validation
| Format | Tool |
|---|---|
| JSON | JSON Schema (networknt/json-schema-validator) |
| XML | XSD |
| Avro/Protobuf | Schema registry (Confluent, Buf) |
| OpenAPI | Request/response validation middleware |
10. Understanding Fail-Fast vs Collect-All Validation
| Mode | When |
|---|---|
| Fail-fast | Internal calls; quick error |
| Collect-all | UI forms; show all errors at once |
| Bean Validation default | Collect-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
| Where | What |
|---|---|
| Constructor | Reject invalid construction |
| Mutator | Reject invalid transitions |
| Aggregate root | Multi-entity invariants |
Note: Always-valid domain objects mean callers cannot construct an invalid state.