A Java implementation of Scala's Either type for functional error handling and representing values with two possibilities.
Either is a type that represents a value of one of two possible types. It's commonly used for error handling where:
- Left represents an error or failure case
- Right represents a success case
This is a type-safe alternative to throwing exceptions or returning null values.
- Type-safe error handling without exceptions
- Right-biased monadic operations (map, flatMap)
- Immutable and thread-safe
- Null-safe (rejects null values)
- Java 17+ with sealed interfaces and records
- Comprehensive API matching Scala's Either
- Zero external dependencies - only uses Java standard library
- Small footprint - single file implementation
- Java 17 or higher
- Maven 3.6+
<dependency>
<groupId>stream.header</groupId>
<artifactId>either</artifactId>
<version>0.1.0</version>
</dependency>git clone <repository-url>
cd either
./mvnw clean installimport stream.header.Either;
// Create Either instances
Either<String, Integer> success = Either.right(42);
Either<String, Integer> failure = Either.left("Something went wrong");
// Transform success values
Either<String, Integer> doubled = success.map(x -> x * 2); // Right(84)
// Handle both cases
String result = success.fold(
error -> "Error: " + error,
value -> "Success: " + value
);Replace exception-based code:
// Traditional approach with exceptions
public int parseAndDouble(String str) throws NumberFormatException {
return Integer.parseInt(str) * 2;
}
// Either approach
public Either<String, Integer> parseAndDouble(String str) {
try {
return Either.right(Integer.parseInt(str) * 2);
} catch (NumberFormatException e) {
return Either.left("Invalid number: " + str);
}
}Either<String, Integer> result = parseNumber("10")
.map(x -> x * 2) // Transform success
.flatMap(x -> divide(x, 5)) // Chain operations
.filterOrElse(
x -> x > 0,
() -> "Result must be positive"
);
// Extract value with default
int value = result.getOrElse(0);public Either<String, User> createUser(String email, int age) {
return validateEmail(email)
.flatMap(validEmail -> validateAge(age)
.map(validAge -> new User(validEmail, validAge)));
}
private Either<String, String> validateEmail(String email) {
return email.contains("@")
? Either.right(email)
: Either.left("Invalid email");
}
private Either<String, Integer> validateAge(int age) {
return age >= 18
? Either.right(age)
: Either.left("Must be 18 or older");
}Either<String, Integer> result = someOperation();
// Using fold for pattern matching
String message = result.fold(
error -> "Operation failed: " + error,
value -> "Operation succeeded with: " + value
);
// Using conditional checks
if (result.isRight()) {
int value = result.getRight();
System.out.println("Success: " + value);
} else {
String error = result.getLeft();
System.err.println("Error: " + error);
}Either.left(value)- Create a Left instanceEither.right(value)- Create a Right instance
map(mapper)- Transform the Right valuemapLeft(mapper)- Transform the Left valueflatMap(mapper)- Chain Either-returning operations
fold(leftMapper, rightMapper)- Apply function based on Left/RightgetOrElse(defaultValue)- Get Right value or defaultorElse(alternative)- Get this or alternative EithergetLeft()/getRight()- Unsafe extraction (throws if wrong side)
filterOrElse(predicate, zeroSupplier)- Filter Right valuecontains(value)- Check if Right equals valueexists(predicate)- Test if Right satisfies predicate
forEach(action)- Perform action on Right valueforEachLeft(action)- Perform action on Left value
toOptional()- Convert Right to OptionaltoOptionalLeft()- Convert Left to Optionalswap()- Exchange Left and Right sides
isLeft()- Check if this is a LeftisRight()- Check if this is a Right
This implementation follows modern functional programming principles:
-
Right-biased: Operations like
mapandflatMapoperate on the Right value, treating Left as a short-circuit value (similar to Scala 2.12+) -
Immutable: All operations return new instances rather than modifying existing ones
-
Null-safe: Both Left and Right reject null values, preventing NullPointerExceptions
-
Type-safe: Compile-time guarantees about which operations are valid
-
Sealed types: Exhaustive pattern matching support via Java sealed interfaces
Use Either when:
- You need type-safe error handling
- You want to avoid throwing exceptions for expected failures
- You need to accumulate errors or success values
- You want to chain operations that might fail
Don't use Either when:
- You're dealing with truly exceptional conditions (use exceptions)
- You only need to represent presence/absence (use
Optional) - You're working with asynchronous operations (consider
CompletableFuture)
This project is licensed under the MIT License - see the LICENSE.txt file for details.
Copyright (c) 2025 stream.header.either contributors
Contributions are welcome! Please feel free to submit a Pull Request.