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)) are default methods, so guards can be defined as lambdas and
composed without inheritance. 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 TypeMethodDescriptionReturns a composed guard that requires both this guard andotherto pass.Returns a composed guard that evaluatesnextonly when this guard 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<NonEmptyList<String>, 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<NonEmptyList<String>, 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.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.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.- 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");- Type Parameters:
T- the value type- Parameters:
predicate- the condition that must hold for the value to be validerrorMessage- 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 );- Type Parameters:
T- the value type- Parameters:
predicate- the condition that must hold for the value to be validerrorMessageFn- function that produces an error message from the failing value- Returns:
- a new
Guard<T> - Throws:
NullPointerException- ifpredicateorerrorMessageFnisnull
-
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 collected- 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.- 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"])- 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
-
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
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"]- 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<NonEmptyList<String>, 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()Not suitable when
nullis a valid success value. This method callsOptional.of(value)on the validated value, which throwsNullPointerExceptionifvalueisnull. Under@NullMarked,Tis non-null by default, so this is safe for ordinary guards. However, ifTis a nullable type (e.g.,Guard<@Nullable String>) and the guard can returnValid(null), calling this method will throwNullPointerException. In that case, usecheck(value)and work with the resultingValidateddirectly, applyingOptional.ofNullable(Object)to the extracted value when needed.- Parameters:
value- the value to validate- Returns:
Optional.of(value)if the guard passes,Optional.empty()if the guard fails
-
nonNull
-