ValidationPath
ValidationPath<E, A> wraps Validated<E, A> for computations that
accumulate errors instead of short-circuiting on the first failure.
- Creating ValidationPath instances
- Error accumulation with zipWithAccum
- Semigroup for combining errors
- Extraction patterns
- When to use (and when not to)
Creation
// Valid value
ValidationPath<List<String>, Integer> valid =
Path.valid(42, Semigroups.list());
// Invalid value with errors
ValidationPath<List<String>, Integer> invalid =
Path.invalid(List.of("Error 1", "Error 2"), Semigroups.list());
// From existing Validated
ValidationPath<String, User> user =
Path.validation(validatedUser, Semigroups.first());
The Semigroup<E> parameter defines how errors combine when multiple
validations fail. Common choices:
Semigroups.list()- concatenate error listsSemigroups.string("; ")- join strings with separator
Core Operations
ValidationPath<List<String>, String> name =
Path.valid("Alice", Semigroups.list());
// Transform (same as other paths)
ValidationPath<List<String>, Integer> length = name.map(String::length);
// Chain with via (short-circuits on first error)
ValidationPath<List<String>, String> upper =
name.via(s -> Path.valid(s.toUpperCase(), Semigroups.list()));
Error Accumulation: The Point of It All
The key operation is zipWithAccum, which collects all errors:
ValidationPath<List<String>, String> nameV = validateName(input.name());
ValidationPath<List<String>, String> emailV = validateEmail(input.email());
ValidationPath<List<String>, Integer> ageV = validateAge(input.age());
// Accumulate ALL errors (does not short-circuit)
ValidationPath<List<String>, User> userV = nameV.zipWith3Accum(
emailV,
ageV,
User::new
);
// If name and email both fail:
// Invalid(["Name too short", "Invalid email format"])
// NOT just Invalid(["Name too short"])
Compare with zipWith, which short-circuits:
// Short-circuits: only first error returned
ValidationPath<List<String>, User> shortCircuit =
nameV.zipWith(emailV, ageV, User::new);
Combining Validations
// andAlso runs both, accumulating errors, keeping first value if both valid
ValidationPath<List<String>, String> thorough =
checkNotEmpty(name)
.andAlso(checkMaxLength(name, 100))
.andAlso(checkNoSpecialChars(name));
// All three checks run; all errors collected
Extraction
ValidationPath<List<String>, User> path = validateUser(input);
Validated<List<String>, User> validated = path.run();
String result = validated.fold(
errors -> "Errors: " + String.join(", ", errors),
user -> "Valid user: " + user.name()
);
When to Use
ValidationPath is right when:
- You want users to see all validation errors at once
- Multiple independent checks must all run
- Form validation, batch processing, comprehensive error reports
- Being kind to users matters (it does)
ValidationPath is wrong when:
- You only need the first error → use EitherPath
- Subsequent validations depend on earlier ones passing → use EitherPath with
via
- Validated - Underlying type for ValidationPath
- EitherPath - For short-circuit validation
- Semigroup and Monoid - How errors combine