Interface Guard<T>
- Type Parameters:
T- the type of value being validated
- Functional Interface:
- This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
Validated result when applied to a value.
Guard<T> is a @FunctionalInterface whose single abstract method is
check(T), which returns
Validated<NonEmptyList<String>, T>: Valid(value) when the predicate passes,
or Invalid(errors) when it fails.
All composition operators (and(Guard), or(Guard), negate(), andThen(Guard),
contramap(Function), withMessage(String)) are default methods, so guards can be
defined as lambdas and composed without inheritance; allOf(Guard, Guard...) and anyOf(Guard, Guard...)
compose several guards at once. The choice to use @FunctionalInterface with
default methods rather than an abstract class is documented in
ADR-011 — Guard<T> as a @FunctionalInterface with default methods.
Guards are designed to be defined once and reused across validation pipelines, eliminating
the repetitive if/Validated.invalidNel(Object) pattern:
Guard<String> notBlank = Guard.of(s -> !s.isBlank(), "must not be blank");
Guard<String> minLength3 = Guard.of(s -> s.length() >= 3, "must be at least 3 chars");
Guard<String> alphanumeric = Guard.of(s -> s.matches("[\\w]+"), "must be alphanumeric");
Guard<String> username = notBlank.and(minLength3).and(alphanumeric);
username.check("al"); // Invalid(["must be at least 3 chars"])
username.check("ok?"); // Invalid(["must be alphanumeric"])
username.check("alice"); // Valid("alice")
Error type
The error type is fixed as NonEmptyList<String> — human-readable messages
accumulated across all failing guards. This design avoids requiring a BinaryOperator<E>
for merging in and(Guard)/or(Guard), and guarantees at least one error is always present.
The trade-off is that typed domain error objects require working directly with
Validated<E, A> instead. This decision is documented in
ADR-005 — Guard<T> accumulates errors as a fixed NonEmptyList<String>.
Composition semantics
and— both guards must pass; errors from all failing guards are accumulated (not fail-fast).or— the first passing guard short-circuits; if all fail, all errors are accumulated.negate/negate(message)/negate(messageFromValue)— inverts the predicate.
-
Method Summary
Modifier and TypeMethodDescriptionstatic <T> Guard<T> Returns a guard that passes only when all of the given guards pass.Returns a composed guard that requires both this guard andotherto pass.Returns a composed guard that evaluatesnextonly when this guard passes.static <T> Guard<T> Returns a guard that passes when at least one of the given guards passes.Applies this guard tovalue.default Either<NonEmptyList<String>, T> checkToEither(T value) Applies this guard tovalueand returns anEither<NonEmptyList<String>, T>.checkToOption(T value) Applies this guard tovalueand returns anOption<T>.checkToOptional(T value) Applies this guard tovalueand returns a standardOptional<T>.default Result<T, NonEmptyList<String>> checkToResult(T value) Applies this guard tovalueand returns aResult<T, NonEmptyList<String>>.checkToResult(T value, Function<? super NonEmptyList<String>, ? extends E> toError) Applies this guard tovalueand returns aResult<T, E>, mapping the accumulated error list to a domain-specific error type viatoError.checkToTry(T value) Applies this guard tovalueand returns aTry<T>.checkToTry(T value, Function<? super NonEmptyList<String>, ? extends X> toThrowable) Applies this guard tovalueand returns aTry<T>, converting any accumulated errors to a domain-specificThrowableviatoThrowable.default <U> Guard<U> Returns aGuard<U>that appliesmapperto its input before checking.default <U> Guard<U> Returns aGuard<U>that appliesmapperto its input before checking, prefixing every error message withfieldName.mapMessages(Function<? super String, String> transform) Returns a guard that rewrites every error message produced by this guard withtransform, leaving valid results untouched.static <T> Guard<T> Adapts aGuard<? super T>to aGuard<T>by re-wrapping the checked value, preserving the accumulated errors on failure.negate()Returns a guard that is the logical negation of this guard, using a generic error message.Returns a guard that is the logical negation of this guard, using the supplied error message when the original guard passes.Returns a guard that is the logical negation of this guard, using a dynamic error message.nonNull()Creates and returns a Guard instance that ensures a value is non-null.static <T> Guard<T> Creates aGuard<T>from a predicate and a static error message.static <T> Guard<T> Creates aGuard<T>from a predicate and a dynamic error message function.static <T> Guard<T> ofCatching(Predicate<? super T> predicate, String errorMessage) Creates aGuard<T>from a predicate that may throw, treating a thrownRuntimeExceptionas a failed check.Returns a composed guard that passes when at least one of this guard orotherpasses.withMessage(String message) Returns a guard that replaces any error messages produced by this guard withmessage.
-
Method Details
-
check
Applies this guard tovalue.Contract: a guard is a validator, not a transformer — an implementation must return
Validof the checked value itself, never a substituted or normalized one. The composition operators rely on this: they re-wrap the original input, so any value produced by a contract-violating guard is discarded during composition. Note also thatValidrejectsnull, so a guard over a nullable type can never pass anullvalue — it must reject it (seenonNull()).- Parameters:
value- the value to validate- Returns:
Valid(value)if the predicate passes, orInvalid(errors)if it fails
-
of
Creates aGuard<T>from a predicate and a static error message.Example:
Guard<String> notBlank = Guard.of(s -> !s.isBlank(), "must not be blank");The predicate must not throw: any exception it raises escapes
check(Object)unwrapped, breaking the contract that a guard always returns aValidated. For predicates that would throw onnullinput, compose withnonNull().andThen(...)so the null check short-circuits first; for predicates that may throw on other inputs (parsing, regex on malformed data), useofCatching(Predicate, String).- Type Parameters:
T- the value type- Parameters:
predicate- the condition that must hold for the value to be valid; must not throwerrorMessage- the error message produced when the predicate fails- Returns:
- a new
Guard<T> - Throws:
NullPointerException- ifpredicateorerrorMessageisnull
-
of
Creates aGuard<T>from a predicate and a dynamic error message function.The
errorMessageFnreceives the failing value so it can produce a context-specific message.Example:
Guard<Integer> max = Guard.of( n -> n <= 100, n -> "must be ≤ 100, got " + n );The predicate must not throw: any exception it raises escapes
check(Object)unwrapped, breaking the contract that a guard always returns aValidated. For predicates that would throw onnullinput, compose withnonNull().andThen(...)so the null check short-circuits first; for predicates that may throw on other inputs (parsing, regex on malformed data), useofCatching(Predicate, String).- Type Parameters:
T- the value type- Parameters:
predicate- the condition that must hold for the value to be valid; must not throwerrorMessageFn- function that produces an error message from the failing value- Returns:
- a new
Guard<T> - Throws:
NullPointerException- ifpredicateorerrorMessageFnisnull
-
ofCatching
Creates aGuard<T>from a predicate that may throw, treating a thrownRuntimeExceptionas a failed check.Unlike
of(Predicate, String), whose predicate must not throw, this factory preserves the contract that a guard always returns aValidated: if the predicate throws aRuntimeException, the guard returnsInvalid([errorMessage])exactly as if the predicate had returnedfalse.Errors and otherThrowables still propagate.Example:
Guard<String> numeric = Guard.ofCatching( s -> Integer.parseInt(s) >= 0, // parseInt throws on non-numeric input "must be a non-negative number"); numeric.check("42"); // Valid("42") numeric.check("abc"); // Invalid(["must be a non-negative number"]) — exception folded- Type Parameters:
T- the value type- Parameters:
predicate- the condition to evaluate; a thrownRuntimeExceptioncounts as a failureerrorMessage- the error message produced when the predicate fails or throws- Returns:
- a new
Guard<T> - Throws:
NullPointerException- ifpredicateorerrorMessageisnull
-
nonNull
-
allOf
Returns a guard that passes only when all of the given guards pass.Same semantics as chaining
and: every guard is always evaluated and errors from all failing guards are accumulated in order — not fail-fast. The first parameter is mandatory, so the composition is never empty. Unlike a manualandchain, the guards are evaluated in a single pass with one error list.Example:
Guard<String> username = Guard.allOf(notBlank, minLength3, alphanumeric); username.check("a?"); // Invalid(["must be at least 3 chars", "must be alphanumeric"]) username.check("alice"); // Valid("alice")- Type Parameters:
T- the value type- Parameters:
first- the first guard; must not benullrest- the remaining guards; must not benullor containnull- Returns:
- a composed
Guard<T>requiring every guard to pass - Throws:
NullPointerException- iffirst,rest, or any element isnull
-
anyOf
Returns a guard that passes when at least one of the given guards passes.Same semantics as chaining
or: evaluation short-circuits on the first passing guard; if every guard fails, errors from all of them are accumulated in order. The first parameter is mandatory, so the composition is never empty.Example:
Guard<String> contact = Guard.anyOf(email, phone); contact.check("alice@example.com"); // Valid — phone never evaluated contact.check("hello"); // Invalid(["must contain @", "must be digits"])- Type Parameters:
T- the value type- Parameters:
first- the first guard; must not benullrest- the remaining guards; must not benullor containnull- Returns:
- a composed
Guard<T>requiring at least one guard to pass - Throws:
NullPointerException- iffirst,rest, or any element isnull
-
narrow
Adapts aGuard<? super T>to aGuard<T>by re-wrapping the checked value, preserving the accumulated errors on failure.Use this when assigning or passing a guard written against a supertype where a
Guard<T>is required outside the variance-aware combinators — the adaptation cannot be written as a plain lambda because theValidatedvalue types differ:Guard<CharSequence> notEmpty = Guard.of(cs -> !cs.isEmpty(), "must not be empty"); Guard<String> forStrings = Guard.narrow(notEmpty);- Type Parameters:
T- the narrower value type- Parameters:
guard- the guard written against a supertype; must not benull- Returns:
- a
Guard<T>delegating toguard - Throws:
NullPointerException- ifguardisnull
-
and
Returns a composed guard that requires both this guard andotherto pass.Both guards are always evaluated — this is not fail-fast. Errors from all failing guards are accumulated into a single
NonEmptyList, so the caller receives a complete picture of all violations at once.Example:
Guard<Integer> positive = Guard.of(n -> n > 0, "must be positive"); Guard<Integer> even = Guard.of(n -> n % 2 == 0, "must be even"); Guard<Integer> positiveEven = positive.and(even); positiveEven.check(4); // Valid(4) positiveEven.check(3); // Invalid(["must be even"]) positiveEven.check(-1); // Invalid(["must be positive", "must be even"]) // — both guards evaluated, both errors collectedThe parameter is contravariant (
Guard<? super T>), mirroringPredicate.and, so a guard written against a supertype (e.g.Guard<CharSequence>) can be composed into aGuard<String>.- Parameters:
other- the guard that must also pass; must not benull- Returns:
- a composed
Guard<T> - Throws:
NullPointerException- ifotherisnull
-
andThen
Returns a composed guard that evaluatesnextonly when this guard passes.Unlike
and, evaluation is short-circuit: if this guard returnsInvalid,nextis never called and its error is never accumulated. This makesandThenthe safe choice when the downstream guard's predicate would throw on the values rejected by this guard — most notably when composingnonNull()with a rule that dereferences the value:Guard<@Nullable String> nonNullAndNotBlank = Guard.<@Nullable String>nonNull() .andThen(Guard.<@Nullable String>of(s -> s != null && !s.isBlank(), "must not be blank")); nonNullAndNotBlank.check("hello"); // Valid("hello") nonNullAndNotBlank.check(null); // Invalid(["must not be null"]) — next not evaluated nonNullAndNotBlank.check(" "); // Invalid(["must not be blank"])Use
andwhen you want both guards evaluated regardless of the first result (error accumulation). UseandThenwhen the second guard must not run until the first has passed.The parameter is contravariant (
Guard<? super T>); the composed guard re-wraps the original value, so the result type staysGuard<T>.- Parameters:
next- the guard to evaluate when this guard passes; must not benull- Returns:
- a composed
Guard<T> - Throws:
NullPointerException- ifnextisnull
-
or
Returns a composed guard that passes when at least one of this guard orotherpasses.Evaluation is short-circuit: if this guard passes,
otheris never evaluated. If both fail, errors from both guards are accumulated.Example:
Guard<String> email = Guard.of(s -> s.contains("@"), "must contain @"); Guard<String> phone = Guard.of(s -> s.matches("\\d+"), "must be digits"); Guard<String> contact = email.or(phone); contact.check("alice@example.com"); // Valid — email passes, phone not evaluated contact.check("12345"); // Valid — phone passes contact.check("hello"); // Invalid(["must contain @", "must be digits"])The parameter is contravariant (
Guard<? super T>), mirroringPredicate.or; the composed guard re-wraps the original value, so the result type staysGuard<T>.- Parameters:
other- the alternative guard; must not benull- Returns:
- a composed
Guard<T> - Throws:
NullPointerException- ifotherisnull
-
negate
Returns a guard that is the logical negation of this guard, using a generic error message.The composed guard returns
Valid(value)when this guard fails, andInvalid(["must not satisfy the condition"])when this guard passes. Usenegate(message)to supply a domain-specific error message.- Returns:
- the negated
Guard<T>
-
negate
Returns a guard that is the logical negation of this guard, using the supplied error message when the original guard passes.Example:
Guard<String> notAdmin = Guard.of(s -> s.equals("admin"), "is admin") .negate("username must not be 'admin'"); notAdmin.check("alice"); // Valid("alice") notAdmin.check("admin"); // Invalid(["username must not be 'admin'"])- Parameters:
errorMessage- the error message returned when the original guard passes- Returns:
- the negated
Guard<T> - Throws:
NullPointerException- iferrorMessageisnull
-
negate
Returns a guard that is the logical negation of this guard, using a dynamic error message.The
messageFromValuefunction receives the value that passed the original guard, allowing a context-specific error message.Example:
Guard<String> notReserved = Guard.of(s -> s.equals("admin") || s.equals("root"), "") .negate(s -> "username '" + s + "' is reserved"); notReserved.check("alice"); // Valid("alice") notReserved.check("admin"); // Invalid(["username 'admin' is reserved"])- Parameters:
messageFromValue- function producing an error message from the passing value; must not benull- Returns:
- the negated
Guard<T> - Throws:
NullPointerException- ifmessageFromValueisnull
-
withMessage
Returns a guard that replaces any error messages produced by this guard withmessage.Useful when you want to expose a single clean message at a public API boundary regardless of the internal validation details.
Example:
Guard<String> username = notBlank.and(minLength3).withMessage("invalid username"); username.check(""); // Invalid(["invalid username"]) username.check("a"); // Invalid(["invalid username"]) username.check("alice"); // Valid("alice")- Parameters:
message- the replacement error message; must not benull- Returns:
- a new
Guard<T>that returns a single fixed error when this guard fails - Throws:
NullPointerException- ifmessageisnull
-
asPredicate
Returns a standardPredicate<T>that returnstruewhen this guard passes andfalsewhen it fails.Use this to integrate guards with standard Java APIs that accept
Predicate(e.g.,Stream.filter,Collection.removeIf).Example:
Guard<String> notBlank = Guard.of(s -> !s.isBlank(), "must not be blank"); List<String> valid = Stream.of("alice", " ", "bob", "") .filter(notBlank.asPredicate()) .toList(); // ["alice", "bob"]- Returns:
- a
Predicate<T>backed by this guard
-
contramap
Returns aGuard<U>that appliesmapperto its input before checking.This is the contravariant map operation: it adapts a guard written for type
Tto work on an enclosing typeUby projectingU → Tfirst. It is the idiomatic way to reuse field-level guards on whole objects.Example:
Guard<String> notBlank = Guard.of(s -> !s.isBlank(), "username must not be blank"); // Lift notBlank to validate User objects by their username field Guard<User> userGuard = notBlank.contramap(User::username); userGuard.check(new User("alice")); // Valid(user) userGuard.check(new User(" ")); // Invalid(["username must not be blank"])- Type Parameters:
U- the input type of the returned guard- Parameters:
mapper- function that extracts theTvalue from aU; must not benull- Returns:
- a new
Guard<U>that projectsU → Tbefore checking - Throws:
NullPointerException- ifmapperisnull
-
contramap
Returns aGuard<U>that appliesmapperto its input before checking, prefixing every error message withfieldName.Like
contramap(Function), but each accumulated error is rewritten as"fieldName: originalMessage", so messages stay unambiguous when several field-level guards are combined on the same object.Example:
Guard<String> notBlank = Guard.of(s -> !s.isBlank(), "must not be blank"); Guard<User> userGuard = Guard.allOf( notBlank.contramap(User::username, "username"), notBlank.contramap(User::email, "email")); userGuard.check(new User(" ", " ")); // Invalid(["username: must not be blank", "email: must not be blank"])- Type Parameters:
U- the input type of the returned guard- Parameters:
mapper- function that extracts theTvalue from aU; must not benullfieldName- prefix identifying the projected field in error messages; must not benull- Returns:
- a new
Guard<U>that projectsU → Tbefore checking and prefixes errors withfieldName - Throws:
NullPointerException- ifmapperorfieldNameisnull
-
mapMessages
Returns a guard that rewrites every error message produced by this guard withtransform, leaving valid results untouched.This is the general form behind
contramap(Function, String)'s field prefixing — use it directly for any other message convention: nested paths, i18n keys, suffixes, or structured formats.Example:
Guard<String> username = notBlank.and(minLength3) .mapMessages(m -> "user.name: " + m); username.check(""); // Invalid(["user.name: must not be blank", ...])Unlike
withMessage(String), which collapses all errors into one fixed message,mapMessagestransforms each accumulated message individually.- Parameters:
transform- function applied to each error message; must not benulland must not returnnull- Returns:
- a new
Guard<T>with rewritten error messages - Throws:
NullPointerException- iftransformisnull
-
checkToResult
Applies this guard tovalueand returns aResult<T, NonEmptyList<String>>.Equivalent to
this.check(value).toResult()but removes the need to import and chain the conversion manually.- Parameters:
value- the value to validate- Returns:
Result.ok(value)if the guard passes, orResult.err(errors)if it fails
-
checkToResult
default <E> Result<T,E> checkToResult(T value, Function<? super NonEmptyList<String>, ? extends E> toError) Applies this guard tovalueand returns aResult<T, E>, mapping the accumulated error list to a domain-specific error type viatoError.Use this at domain service boundaries where
Resultis the preferred container and the error type is richer than a plain list of strings.Example:
Guard<String> username = notBlank.and(minLength3); Result<String, ValidationException> result = username.checkToResult( input, errors -> new ValidationException("username", errors.toList()) );- Type Parameters:
E- the domain error type- Parameters:
value- the value to validatetoError- function mapping the accumulated error list toE- Returns:
Result.ok(value)on success, orResult.err(toError(errors))on failure- Throws:
NullPointerException- iftoErrorisnull
-
checkToOption
Applies this guard tovalueand returns anOption<T>.Returns
Some(value)when the guard passes andNonewhen it fails, discarding the error details. Use this when you only need to know whether a value is valid, not why it is not.Example:
Guard<String> notBlank = Guard.of(s -> !s.isBlank(), "must not be blank"); // Filter a stream keeping only valid values List<String> valid = Stream.of("alice", " ", "bob") .flatMap(s -> notBlank.checkToOption(s).stream()) .toList(); // ["alice", "bob"]A guard can never produce
Valid(null)—Validated.Validrejectsnullat construction — so the returnedOptionnever wrapsnull; a guard over a nullable type must rejectnull(seecheck(Object)).- Parameters:
value- the value to validate- Returns:
Option.some(value)if the guard passes, orOption.none()if it fails
-
checkToEither
Applies this guard tovalueand returns anEither<NonEmptyList<String>, T>.Returns
Either.right(value)when the guard passes andEither.left(errors)when it fails. Use this when downstream logic is already expressed in terms ofEither.Example:
Guard<String> notBlank = Guard.of(s -> !s.isBlank(), "must not be blank"); Either<NonEmptyList<String>, String> right = notBlank.checkToEither("hello"); // Either.right("hello") Either<NonEmptyList<String>, String> left = notBlank.checkToEither(" "); // Either.left(NonEmptyList.of("must not be blank"))- Parameters:
value- the value to validate- Returns:
Either.right(value)if the guard passes, orEither.left(errors)if it fails
-
checkToTry
Applies this guard tovalueand returns aTry<T>.Returns
Try.success(value)when the guard passes. When it fails, the accumulated error messages are joined with"; "and wrapped in anIllegalArgumentException. UsecheckToTry(Object, Function)to supply a domain-specific exception instead.Example:
Guard<String> notBlank = Guard.of(s -> !s.isBlank(), "must not be blank"); Try<String> success = notBlank.checkToTry("hello"); // Try.success("hello") Try<String> failure = notBlank.checkToTry(" "); // Try.failure(new IllegalArgumentException("must not be blank"))- Parameters:
value- the value to validate- Returns:
Try.success(value)if the guard passes, or aTry.failurewrapping anIllegalArgumentExceptionwith the joined error messages
-
checkToTry
default <X extends Throwable> Try<T> checkToTry(T value, Function<? super NonEmptyList<String>, ? extends X> toThrowable) Applies this guard tovalueand returns aTry<T>, converting any accumulated errors to a domain-specificThrowableviatoThrowable.Use this at boundaries where the caller controls the exception type.
Example:
Guard<String> username = notBlank.and(minLength3); Try<String> result = username.checkToTry( input, errors -> new ValidationException(errors.toList()) );- Type Parameters:
X- the throwable type- Parameters:
value- the value to validatetoThrowable- function mapping the accumulated error list to aThrowable; must not benull- Returns:
Try.success(value)if the guard passes, orTry.failure(toThrowable(errors))if it fails- Throws:
NullPointerException- iftoThrowableisnull
-
checkToOptional
Applies this guard tovalueand returns a standardOptional<T>.Returns
Optional.of(value)when the guard passes andOptional.empty()when it fails, discarding the error details. Use this to integrate with Java standard library APIs that work withOptional.Example:
Guard<String> notBlank = Guard.of(s -> !s.isBlank(), "must not be blank"); Optional<String> present = notBlank.checkToOptional("hello"); // Optional.of("hello") Optional<String> empty = notBlank.checkToOptional(" "); // Optional.empty()A guard can never produce
Valid(null)—Validated.Validrejectsnullat construction — so this method never passesnulltoOptional.of(Object); a guard over a nullable type must rejectnull(seecheck(Object)). Note thatOptional.of(value)wraps the input: for aGuard<@Nullable T>checking anullinput, the guard must returnInvalid, which yieldsOptional.empty()here.- Parameters:
value- the value to validate- Returns:
Optional.of(value)if the guard passes,Optional.empty()if the guard fails
-