Validation
validate() evaluates all field rules together and short-circuits with a 422 response if any fail. You can validate JSON body fields, query parameters, and path parameters.
Pipeline Position
Section titled “Pipeline Position”validate() is only available on HttpStream — the stream type you receive at the start of a handler. As soon as you call any transformation step (map, flatMap, blockingMap, blockingFlatMap, asyncMap, …), the stream is downgraded to an HttpMapStream, which does not expose validate().
In practice this means validation must run before any other pipeline step. It can only be used on the first step(s) of the pipeline. You may chain multiple validate() calls together, and you may combine them with requireJwt(), but once you transform the request you can no longer add validation.
// OK — validate() is the first stepreturn e .validate(v -> { v.field("name", r -> r.name).required().minLength(2); v.field("email", r -> r.email).required().email(); }) .map(ctx -> buildUser(ctx.in())) .complete(ctx -> HttpResult.success(ctx.in()));// Also OK — validate() after requireJwt() (both live on HttpStream)return e .requireJwt(jwtProvider) .validate(v -> v.field("name", r -> r.name).required()) .map(ctx -> buildUser(ctx.in())) .complete(ctx -> HttpResult.success(ctx.in()));// Does not compile — validate() is not defined on HttpMapStreamreturn e .map(ctx -> ctx.in()) .validate(v -> v.field("name", r -> r.name).required()) // compile error .complete(ctx -> HttpResult.success(ctx.in()));Basic Validation
Section titled “Basic Validation”Inside a validate() block, use field() for body fields, queryParam() for query string parameters, and pathParam() for path parameters:
.validate(v -> { v.field("name", r -> r.name).required().minLength(2); v.field("email", r -> r.email).required().email(); v.field("age", r -> r.age).required().min(0).max(150); v.queryParam("page").required().matches("[0-9]+"); v.pathParam("userId").required().matches("[0-9]+");})Nested Objects
Section titled “Nested Objects”Nested objects use a field overload that takes a validation block for the nested type. The block is only evaluated when the nested value is non-null, and error keys are prefixed with the parent field name:
v.field("address", r -> r.address, a -> { a.field("city", x -> x.city).required(); a.field("zip", x -> x.zip).required().matches("[0-9]{5}");});Lists are validated with listField, which supports size constraints and per-element validation via each():
v.listField("addresses", r -> r.addresses) .required() .minSize(1) .maxSize(10) .each(a -> { a.field("city", x -> x.city).required(); a.field("zip", x -> x.zip).required().matches("[0-9]{5}"); });Element errors are keyed by index, e.g. addresses[0].city.
Error Response Format
Section titled “Error Response Format”On failure the response status is 422 Unprocessable Entity and the body is:
{ "message": "Validation failed", "errors": { "name": ["must not be blank"], "email": ["must be a valid email address"], "address.zip": ["must match pattern: [0-9]{5}"] }}Available Rules
Section titled “Available Rules”String Rules
Section titled “String Rules”| Rule | Description |
|---|---|
required() | Must not be null or blank |
minLength(n) | Minimum string length |
maxLength(n) | Maximum string length |
email() | Must be a valid email address |
matches(regex) | Must match the given regex pattern |
Numeric Rules
Section titled “Numeric Rules”| Rule | Description |
|---|---|
required() | Must not be null |
min(n) | Minimum value |
max(n) | Maximum value |
List Rules
Section titled “List Rules”| Rule | Description |
|---|---|
required() | Must not be null |
minSize(n) | Minimum list size |
maxSize(n) | Maximum list size |
each(block) | Validate each element |