Object-functional language extension to Java 8+ providing persistent collections, functional abstractions, and monadic control types.
—
Monadic control structures for error handling, optional values, and validation that eliminate null pointer exceptions and provide functional composition patterns.
Container for values that may or may not exist, eliminating null pointer exceptions with functional composition.
/**
* Container for optional values - either Some(value) or None
*/
interface Option<T> extends Value<T> {
// Factory methods
static <T> Option<T> of(T value); // Create Some(value) or None if value is null
static <T> Option<T> some(T value); // Create Some(value), throws if null
static <T> Option<T> none(); // Create None instance
static <T> Option<T> when(boolean condition, T value); // Conditional creation
static <T> Option<T> when(boolean condition, Supplier<? extends T> supplier);
// State checking
boolean isDefined(); // true if Some, false if None
boolean isEmpty(); // true if None, false if Some
// Value access
T get(); // Get value, throws if None
T getOrElse(T other); // Get value or return other if None
T getOrElse(Supplier<? extends T> supplier); // Get value or compute other if None
<X extends Throwable> T getOrElseThrow(Supplier<X> exceptionSupplier) throws X;
Option<T> orElse(Option<? extends T> other); // Return this if Some, other if None
Option<T> orElse(Supplier<? extends Option<? extends T>> supplier);
// Transformation operations
<U> Option<U> map(Function<? super T, ? extends U> mapper);
<U> Option<U> flatMap(Function<? super T, ? extends Option<? extends U>> mapper);
Option<T> filter(Predicate<? super T> predicate);
Option<T> filterNot(Predicate<? super T> predicate);
// Side effects
Option<T> peek(Consumer<? super T> action); // Perform action if Some
// Conversion operations
Either<Throwable, T> toEither();
Try<T> toTry();
List<T> toList(); // Empty list if None, single-element if Some
java.util.Optional<T> toJavaOptional();
// Combining operations
<U> Option<Tuple2<T, U>> zip(Option<? extends U> that);
<U, R> Option<R> zipWith(Option<? extends U> that, BiFunction<? super T, ? super U, ? extends R> mapper);
}
/**
* Some variant containing a value
*/
class Some<T> implements Option<T> {
T get(); // Returns the contained value
}
/**
* None variant representing absence of value
*/
class None<T> implements Option<T> {
static None<Object> instance(); // Singleton None instance
}Usage Examples:
import io.vavr.control.Option;
// Creating Options
Option<String> some = Option.of("Hello");
Option<String> none = Option.of(null); // Creates None
Option<Integer> conditional = Option.when(5 > 3, 42); // Creates Some(42)
// Safe operations
String result = some
.map(String::toUpperCase)
.filter(s -> s.length() > 3)
.getOrElse("DEFAULT");
// Chaining operations
Option<Integer> length = some.map(String::length);
Option<String> doubled = some.flatMap(s ->
s.isEmpty() ? Option.none() : Option.some(s + s));
// Pattern matching style
String describe = some.fold(
() -> "No value",
value -> "Value: " + value
);
// Converting to other types
java.util.Optional<String> javaOpt = some.toJavaOptional();
List<String> list = some.toList(); // [Hello] or empty list
// Combining Options
Option<String> firstName = Option.of("John");
Option<String> lastName = Option.of("Doe");
Option<String> fullName = firstName.zipWith(lastName, (f, l) -> f + " " + l);Represents a value that can be one of two types - typically used for error handling where Left represents error and Right represents success.
/**
* Disjoint union type - either Left(error) or Right(value)
*/
interface Either<L, R> extends Value<R> {
// Factory methods
static <L, R> Either<L, R> left(L left); // Create Left instance
static <L, R> Either<L, R> right(R right); // Create Right instance
static <L, R> Either<L, R> cond(boolean test, R right, L left); // Conditional creation
// State checking
boolean isLeft(); // true if Left, false if Right
boolean isRight(); // true if Right, false if Left
// Value access
R get(); // Get right value, throws if Left
L getLeft(); // Get left value, throws if Right
R getOrElseGet(Function<? super L, ? extends R> other); // Get right or compute from left
L getLeftOrElseGet(Function<? super R, ? extends L> other); // Get left or compute from right
<X extends Throwable> R getOrElseThrow(Function<? super L, X> exceptionFunction) throws X;
void orElseRun(Consumer<? super L> action); // Run action if Left
// Alternative operations
Either<L, R> orElse(Either<? extends L, ? extends R> other); // Use other if Left
Either<L, R> orElse(Supplier<? extends Either<? extends L, ? extends R>> supplier);
// Transformation operations
<U> Either<L, U> map(Function<? super R, ? extends U> mapper); // Transform right value
<U> Either<U, R> mapLeft(Function<? super L, ? extends U> leftMapper); // Transform left value
<L2, R2> Either<L2, R2> bimap(Function<? super L, ? extends L2> leftMapper,
Function<? super R, ? extends R2> rightMapper);
<U> Either<L, U> flatMap(Function<? super R, ? extends Either<L, ? extends U>> mapper);
// Filtering
Option<Either<L, R>> filter(Predicate<? super R> predicate);
Option<Either<L, R>> filterOrElse(Predicate<? super R> predicate, L zero);
// Side effects
Either<L, R> peek(Consumer<? super R> action); // Perform action if Right
Either<L, R> peekLeft(Consumer<? super L> action); // Perform action if Left
// Swapping and projections
Either<R, L> swap(); // Swap Left and Right
LeftProjection<L, R> left(); // Project to Left operations
RightProjection<L, R> right(); // Project to Right operations
// Folding operations
<U> U fold(Function<? super L, ? extends U> leftMapper,
Function<? super R, ? extends U> rightMapper);
// Conversion operations
Option<R> toOption(); // Right becomes Some, Left becomes None
Try<R> toTry(); // Assumes Left contains Throwable
Validation<L, R> toValidation();
}
/**
* Left variant containing error/left value
*/
class Left<L, R> implements Either<L, R> {
L getLeft(); // Returns the left value
}
/**
* Right variant containing success/right value
*/
class Right<L, R> implements Either<L, R> {
R get(); // Returns the right value
}
/**
* Left projection for operating on left values
*/
class LeftProjection<L, R> {
<U> Either<U, R> map(Function<? super L, ? extends U> mapper);
<U> Either<U, R> flatMap(Function<? super L, ? extends Either<? extends U, R>> mapper);
Option<L> filter(Predicate<? super L> predicate);
L getOrElseGet(Function<? super R, ? extends L> other);
}
/**
* Right projection for operating on right values
*/
class RightProjection<L, R> {
<U> Either<L, U> map(Function<? super R, ? extends U> mapper);
<U> Either<L, U> flatMap(Function<? super R, ? extends Either<L, ? extends U>> mapper);
Option<R> filter(Predicate<? super R> predicate);
R getOrElseGet(Function<? super L, ? extends R> other);
}Usage Examples:
import io.vavr.control.Either;
// Creating Either instances
Either<String, Integer> success = Either.right(42);
Either<String, Integer> failure = Either.left("Error occurred");
// Error handling pattern
Either<String, String> result = parseInteger("123")
.map(i -> "Parsed: " + i)
.mapLeft(error -> "Failed: " + error);
// Chaining operations (right-biased)
Either<String, Integer> doubled = success
.flatMap(value -> value > 0 ? Either.right(value * 2) : Either.left("Negative number"));
// Folding both sides
String message = result.fold(
error -> "Error: " + error,
value -> "Success: " + value
);
// Working with projections
String leftResult = failure.left().map(String::toUpperCase).getOrElseGet(i -> "No error");
Integer rightResult = success.right().map(i -> i + 1).getOrElseGet(err -> 0);
// Converting between types
Option<Integer> opt = success.toOption(); // Some(42) if Right, None if Left
Try<Integer> attempt = success.toTry(); // Success(42) if Right, Failure if Left
// Helper method example
static Either<String, Integer> parseInteger(String str) {
try {
return Either.right(Integer.parseInt(str));
} catch (NumberFormatException e) {
return Either.left("Invalid number: " + str);
}
}Represents a computation that may succeed with a value or fail with an exception, providing functional exception handling.
/**
* Computation that may succeed or fail - either Success(value) or Failure(exception)
*/
interface Try<T> extends Value<T> {
// Factory methods
static <T> Try<T> of(CheckedFunction0<? extends T> supplier); // Try computation
static <T> Try<T> success(T value); // Create Success
static <T> Try<T> failure(Throwable exception); // Create Failure
static <T> Try<T> withResources(CheckedFunction0<? extends T> supplier,
AutoCloseable... closeables); // Try-with-resources
// State checking
boolean isSuccess(); // true if Success, false if Failure
boolean isFailure(); // true if Failure, false if Success
// Value access
T get(); // Get value, throws if Failure
T getOrElse(T other); // Get value or return other if Failure
T getOrElse(Supplier<? extends T> supplier);
<X extends Throwable> T getOrElseThrow(Function<? super Throwable, X> f) throws X;
// Exception access (for Failure)
Throwable getCause(); // Get exception, throws if Success
// Transformation operations
<U> Try<U> map(Function<? super T, ? extends U> mapper);
<U> Try<U> mapTry(CheckedFunction1<? super T, ? extends U> mapper);
<U> Try<U> flatMap(Function<? super T, ? extends Try<? extends U>> mapper);
<U> Try<U> flatMapTry(CheckedFunction1<? super T, ? extends Try<? extends U>> mapper);
// Error handling
Try<T> recover(Function<? super Throwable, ? extends T> recovery);
Try<T> recoverWith(Function<? super Throwable, ? extends Try<? extends T>> recovery);
Try<T> mapFailure(Function<? super Throwable, ? extends Throwable> f);
// Filtering
Try<T> filter(Predicate<? super T> predicate);
Try<T> filterTry(CheckedPredicate<? super T> predicate);
// Side effects
Try<T> peek(Consumer<? super T> action); // Perform action if Success
Try<T> onFailure(Consumer<? super Throwable> action); // Perform action if Failure
// Conversion operations
Either<Throwable, T> toEither(); // Failure -> Left, Success -> Right
Option<T> toOption(); // Failure -> None, Success -> Some
Validation<Throwable, T> toValidation();
// Folding operations
<U> U fold(Function<? super Throwable, ? extends U> failureMapper,
Function<? super T, ? extends U> successMapper);
// Combining operations
<U> Try<Tuple2<T, U>> zip(Try<? extends U> that);
<U, R> Try<R> zipWith(Try<? extends U> that, BiFunction<? super T, ? super U, ? extends R> mapper);
}
/**
* Success variant containing the computed value
*/
class Success<T> implements Try<T> {
T get(); // Returns the successful value
}
/**
* Failure variant containing the exception
*/
class Failure<T> implements Try<T> {
Throwable getCause(); // Returns the exception
}Usage Examples:
import io.vavr.control.Try;
import io.vavr.CheckedFunction1;
// Basic Try operations
Try<Integer> result = Try.of(() -> Integer.parseInt("123"));
Try<Integer> failure = Try.of(() -> Integer.parseInt("abc"));
// Chaining operations
Try<String> processed = result
.map(i -> i * 2)
.map(i -> "Result: " + i)
.recover(ex -> "Error: " + ex.getMessage());
// Exception handling
Try<String> safe = Try.of(() -> riskyOperation())
.recover(throwable -> "Default value")
.map(String::toUpperCase);
// Filtering with exceptions
Try<Integer> positive = result.filter(i -> i > 0); // Becomes Failure if <= 0
// Working with multiple Try instances
Try<String> name = Try.of(() -> getName());
Try<Integer> age = Try.of(() -> getAge());
Try<String> person = name.zipWith(age, (n, a) -> n + " is " + a + " years old");
// Try-with-resources pattern
Try<String> content = Try.withResources(
() -> Files.lines(Paths.get("file.txt")).collect(Collectors.joining("\n")),
Files.newBufferedReader(Paths.get("file.txt"))
);
// Converting to other types
Option<Integer> opt = result.toOption(); // Some(123) or None
Either<Throwable, Integer> either = result.toEither(); // Right(123) or Left(exception)
// Pattern matching style processing
String outcome = result.fold(
exception -> "Failed with: " + exception.getMessage(),
value -> "Succeeded with: " + value
);
// Helper methods with checked exceptions
static String riskyOperation() throws IOException {
// Some operation that might throw
return "success";
}
static CheckedFunction1<String, Integer> parseToInt = Integer::parseInt;Represents validation results that can accumulate multiple errors, useful for form validation and data processing.
/**
* Validation that accumulates errors - either Valid(value) or Invalid(errors)
*/
interface Validation<E, T> extends Value<T> {
// Factory methods
static <E, T> Validation<E, T> valid(T value); // Create Valid instance
static <E, T> Validation<E, T> invalid(E error); // Create Invalid with single error
static <E, T> Validation<E, T> invalid(E... errors); // Create Invalid with multiple errors
static <E, T> Validation<E, T> invalid(Iterable<? extends E> errors);
// Conditional creation
static <E, T> Validation<E, T> fromTry(Try<? extends T> tryValue, Function<Throwable, E> errorMapper);
static <E, T> Validation<E, T> fromEither(Either<E, ? extends T> either);
static <E, T> Validation<E, T> fromOption(Option<? extends T> option, E ifNone);
// State checking
boolean isValid(); // true if Valid, false if Invalid
boolean isInvalid(); // true if Invalid, false if Valid
// Value access
T get(); // Get value, throws if Invalid
T getOrElse(T other); // Get value or return other if Invalid
T getOrElse(Supplier<? extends T> supplier);
// Error access
Seq<E> getError(); // Get errors, throws if Valid
// Transformation operations
<U> Validation<E, U> map(Function<? super T, ? extends U> mapper);
<U> Validation<U, T> mapError(Function<E, U> errorMapper);
<F, U> Validation<F, U> bimap(Function<E, F> errorMapper, Function<? super T, ? extends U> valueMapper);
<U> Validation<E, U> flatMap(Function<? super T, ? extends Validation<E, ? extends U>> mapper);
// Filtering
Validation<E, T> filter(Predicate<? super T> predicate, E ifFalse);
Validation<E, T> filter(Predicate<? super T> predicate, Supplier<? extends E> errorSupplier);
// Combining validations (accumulates errors)
<U> Validation<E, Tuple2<T, U>> zip(Validation<E, ? extends U> that);
<U, R> Validation<E, R> zipWith(Validation<E, ? extends U> that,
BiFunction<? super T, ? super U, ? extends R> mapper);
// Applicative operations for accumulating multiple validations
static <E, T1, T2, R> Validation<E, R> combine(
Validation<E, T1> v1, Validation<E, T2> v2,
BiFunction<T1, T2, R> function);
static <E, T1, T2, T3, R> Validation<E, R> combine(
Validation<E, T1> v1, Validation<E, T2> v2, Validation<E, T3> v3,
Function3<T1, T2, T3, R> function);
// ... up to combine8
// Folding operations
<U> U fold(Function<? super Seq<E>, ? extends U> invalidMapper,
Function<? super T, ? extends U> validMapper);
// Conversion operations
Either<Seq<E>, T> toEither(); // Invalid -> Left(errors), Valid -> Right(value)
Option<T> toOption(); // Invalid -> None, Valid -> Some(value)
Try<T> toTry(); // Invalid -> Failure, Valid -> Success
}
/**
* Valid variant containing the validated value
*/
class Valid<E, T> implements Validation<E, T> {
T get(); // Returns the valid value
}
/**
* Invalid variant containing accumulated errors
*/
class Invalid<E, T> implements Validation<E, T> {
Seq<E> getError(); // Returns the accumulated errors
}Usage Examples:
import io.vavr.control.Validation;
import io.vavr.collection.List;
import static io.vavr.control.Validation.*;
// Form validation example
public class Person {
private final String name;
private final String email;
private final int age;
public Person(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
}
// Individual field validations
static Validation<String, String> validateName(String name) {
return name != null && name.trim().length() >= 2
? valid(name.trim())
: invalid("Name must be at least 2 characters");
}
static Validation<String, String> validateEmail(String email) {
return email != null && email.contains("@")
? valid(email)
: invalid("Email must contain @");
}
static Validation<String, Integer> validateAge(Integer age) {
return age != null && age >= 0 && age <= 150
? valid(age)
: invalid("Age must be between 0 and 150");
}
// Combining validations (accumulates all errors)
static Validation<List<String>, Person> validatePerson(String name, String email, Integer age) {
return Validation.combine(
validateName(name).mapError(List::of),
validateEmail(email).mapError(List::of),
validateAge(age).mapError(List::of)
).ap((n, e, a) -> new Person(n, e, a));
}
// Usage
Validation<List<String>, Person> result = validatePerson("", "invalid-email", -5);
// result.isInvalid() == true
// result.getError() == ["Name must be at least 2 characters", "Email must contain @", "Age must be between 0 and 150"]
// Successful validation
Validation<List<String>, Person> success = validatePerson("John", "john@example.com", 30);
// success.isValid() == true
// success.get() == Person("John", "john@example.com", 30)
// Working with validation results
String message = result.fold(
errors -> "Validation failed: " + errors.mkString(", "),
person -> "Valid person: " + person
);
// Converting to other types
Option<Person> maybePerson = success.toOption();
Either<List<String>, Person> eitherPerson = result.toEither();Install with Tessl CLI
npx tessl i tessl/maven-io-vavr--vavr