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 FailureList<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 JavaCollectorAPI always feeds every element to the accumulator before the finisher runs, all stream elements are always consumed. UseResult.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:
Tryis now@NullMarked; null guards were added throughout.Try.recoverWithvalidates that its callback does not returnnull.Result.toList()andResult.partitioningBy()now explicitly rejectnullstream elements withNullPointerException, matching the contract already established bysequence()andtraverse().Try.sequence/Try.traverseuseCollections.unmodifiableListinstead ofList.copyOfso thatSuccess(null)values — which can arise fromTry.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.