Version 0.0.10 is out. This release focuses on making it easier to work with collections of Result and Try values: you can now fold a stream of results into a single result, partition them into two typed lists, or bridge from Optional — all without writing the boilerplate yourself.


Try — sequence & traverse

When you have multiple independent Try computations, you no longer need to iterate and check each one manually. The new combinators handle the fail-fast pattern for you.

// Collect a list of Try values — stops at the first Failure
List<Try<Integer>> attempts = List.of(
Try.of(() -> Integer.parseInt("1")),
Try.of(() -> Integer.parseInt("2")),
Try.of(() -> Integer.parseInt("oops"))
);
Try<List<Integer>> result = Try.sequence(attempts);
// result.isFailure() == true (NumberFormatException from "oops")

traverse combines the mapping and collecting steps:

List<String> inputs = List.of("1", "2", "3");
Try<List<Integer>> parsed = Try.traverse(inputs, s -> Try.of(() -> Integer.parseInt(s)));
// parsed.isSuccess() == true → [1, 2, 3]

Both combinators have overloads for Iterable and Stream, and both fail fast — as soon as a Failure is encountered the remaining elements are not evaluated.


Optional interop

Bridging from Optional is now a one-liner on both Result and Try.

Result.fromOptional

Optional<User> maybeUser = userRepository.findById(id);
Result<User, NoSuchElementException> result = Result.fromOptional(maybeUser);
// Ok(user) or Err(NoSuchElementException("Optional is empty"))

Try.fromOptional

Try<User> tryUser = Try.fromOptional(maybeUser, () -> new UserNotFoundException(id));
// Success(user) or Failure(UserNotFoundException)

The exception supplier is lazy — it is only called when the Optional is empty.


Result — Stream collectors

Three new additions let you plug Result values directly into the Stream API.

stream()

Turns any Result into a Stream — one element for Ok, empty for Err. Useful for flat-mapping inside a stream pipeline:

List<Integer> values = Stream.of(Result.ok(1), Result.err("bad"), Result.ok(3))
.flatMap(Result::stream)
.toList();
// [1, 3]

toList() collector

Accumulates Stream<Result<V, E>> into a single Result<List<V>, E>. If all elements are Ok the collector returns Ok with an unmodifiable list; the first Err encountered (in encounter order) is returned otherwise.

Result<List<Integer>, String> r =
Stream.of(Result.ok(1), Result.ok(2), Result.ok(3))
.collect(Result.toList());
// Ok([1, 2, 3])

Note: toList() is not fail-fast. Because the Java Collector API always feeds every element to the accumulator before the finisher runs, all stream elements are always consumed. Use Result.sequence(Stream) when you need true short-circuit behaviour.

partitioningBy() collector

Separates a mixed stream into two typed, unmodifiable lists:

Result.Partition<Integer, String> p =
Stream.of(Result.ok(1), Result.err("a"), Result.ok(3), Result.err("b"))
.collect(Result.partitioningBy());
p.oks(); // [1, 3]
p.errors(); // ["a", "b"]

Result.Partition<V, E> is a plain record. Its compact constructor defensively copies both lists via List.copyOf, so neither the source lists nor the lists returned by oks() and errors() can be mutated after construction.


Null-safety improvements

Several null-safety gaps were closed across the release:

  • Try is now @NullMarked; null guards were added throughout.
  • Try.recoverWith validates that its callback does not return null.
  • Result.toList() and Result.partitioningBy() now explicitly reject null stream elements with NullPointerException, matching the contract already established by sequence() and traverse().
  • Try.sequence / Try.traverse use Collections.unmodifiableList instead of List.copyOf so that Success(null) values — which can arise from Try.run() — survive the collection step.

Getting the release

Add the dependency to your build:

// Gradle (Kotlin DSL)
implementation("codes.domix:fun:0.0.10")
<!-- Maven -->
<dependency>
<groupId>codes.domix</groupId>
<artifactId>fun</artifactId>
<version>0.0.10</version>
</dependency>

Full Javadoc is available at /dmx-fun/javadoc/.


Found a bug or have a suggestion? Open an issue on GitHub.