Skip to content

p-ja/either

Repository files navigation

Either

A Java implementation of Scala's Either type for functional error handling and representing values with two possibilities.

What is Either?

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.

Features

  • 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

Requirements

  • Java 17 or higher
  • Maven 3.6+

Installation

Maven

<dependency>
    <groupId>stream.header</groupId>
    <artifactId>either</artifactId>
    <version>0.1.0</version>
</dependency>

Build from Source

git clone <repository-url>
cd either
./mvnw clean install

Quick Start

import 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
);

Usage Examples

Error Handling

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);
    }
}

Chaining Operations

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);

Validation

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");
}

Pattern Matching

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);
}

API Overview

Construction

  • Either.left(value) - Create a Left instance
  • Either.right(value) - Create a Right instance

Transformation

  • map(mapper) - Transform the Right value
  • mapLeft(mapper) - Transform the Left value
  • flatMap(mapper) - Chain Either-returning operations

Extraction

  • fold(leftMapper, rightMapper) - Apply function based on Left/Right
  • getOrElse(defaultValue) - Get Right value or default
  • orElse(alternative) - Get this or alternative Either
  • getLeft() / getRight() - Unsafe extraction (throws if wrong side)

Filtering

  • filterOrElse(predicate, zeroSupplier) - Filter Right value
  • contains(value) - Check if Right equals value
  • exists(predicate) - Test if Right satisfies predicate

Side Effects

  • forEach(action) - Perform action on Right value
  • forEachLeft(action) - Perform action on Left value

Conversion

  • toOptional() - Convert Right to Optional
  • toOptionalLeft() - Convert Left to Optional
  • swap() - Exchange Left and Right sides

Queries

  • isLeft() - Check if this is a Left
  • isRight() - Check if this is a Right

Design Philosophy

This implementation follows modern functional programming principles:

  1. Right-biased: Operations like map and flatMap operate on the Right value, treating Left as a short-circuit value (similar to Scala 2.12+)

  2. Immutable: All operations return new instances rather than modifying existing ones

  3. Null-safe: Both Left and Right reject null values, preventing NullPointerExceptions

  4. Type-safe: Compile-time guarantees about which operations are valid

  5. Sealed types: Exhaustive pattern matching support via Java sealed interfaces

When to Use Either

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)

License

This project is licensed under the MIT License - see the LICENSE.txt file for details.

Copyright (c) 2025 stream.header.either contributors

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

About

A functional Either type for Java, representing a value of one of two possible types

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages