functional-reactive
Made in the European Union

Checked Functions

Functional interfaces that may throw checked exceptions, returning Result on the way out.

Java’s built-in Function, BiFunction, Consumer and friends cannot throw checked exceptions without wrapping. functional-reactive ships drop-in replacements that can throw, and return a Result<T> so failures travel through your pipeline without explicit try/catch.

The family

All live under com.svenruppert.functional.functions.

InterfaceExtendsThrowing methodSafe method (returns)
CheckedFunction<T, R>Function<T, Result<R>>R applyWithException(T) throws ExceptionResult<R> apply(T)
CheckedBiFunction<T1, T2, R>BiFunction<T1, T2, Result<R>>R applyWithException(T1, T2) throws ExceptionResult<R> apply(T1, T2)
CheckedTriFunction<T1, T2, T3, R>โ€”R applyWithException(T1, T2, T3) throws ExceptionResult<R> apply(T1, T2, T3)
CheckedSupplier<T>Supplier<Result<T>>T getWithException() throws ExceptionResult<T> get()
CheckedConsumer<T>CheckedFunction<T, Void>inheritedinherited
CheckedExecutorโ€”void applyWithException() throws ExceptionResult<Void> execute()
CheckedPredicate<T>โ€”boolean testWithExceptions(T) throws Exceptionboolean test(T) (returns false on throw)

The key design choice: every Checked* interface either extends the matching JDK interface (so it slots into existing APIs) or carries enough surface to be useful on its own. The throwing method is what you implement; the framework wraps it in a try/catch and produces a Result (or false, for predicates).

Lifting a throwing method reference

The most common usage is a cast from a method reference:

import com.svenruppert.functional.functions.CheckedFunction;
import com.svenruppert.functional.model.Result;

Result<Integer> r1 = ((CheckedFunction<String, Integer>) Integer::parseInt).apply("42");
//   Success(42)

Result<Integer> r2 = ((CheckedFunction<String, Integer>) Integer::parseInt).apply("oops");
//   Failure("For input string: \"oops\"")

Because CheckedFunction<T, R> extends Function<T, Result<R>>, you can also assign it to a plain Function for cleaner call sites:

Function<String, Result<Integer>> parseInt = (CheckedFunction<String, Integer>) Integer::parseInt;

Stream.of("1", "2", "Hi", "3")
      .map(parseInt)                  // Stream<Result<Integer>>
      .flatMap(Result::stream)        // Stream<Integer> โ€” failed parses drop out
      .reduce(Integer::sum)
      .ifPresent(System.out::println); // 6

A small helper: tryIt

When you want even less ceremony at the call site, wrap the cast in a tiny helper:

public static <T, R> Function<T, Result<R>> tryIt(CheckedFunction<T, R> function) {
    Objects.requireNonNull(function);
    return function;
}

Function<String, Result<Integer>> parse = tryIt(Integer::parseInt);
parse.apply("42").ifPresent(System.out::println);   // 42

Or as a function value if you want to compose it:

public static <T, R> Function<CheckedFunction<T, R>, Function<T, Result<R>>> tryIt = f -> f;

Function<String, Result<Integer>> parse = tryIt.apply(Integer::parseInt);

Pick whichever style your codebase prefers.

Sequencing multiple throwing calls

The classic problem: several methods in a row each declare throws Exception, and you want all of them to run even if an earlier one fails (think test teardown):

Function<String, Result<String>> stepA = (CheckedFunction<String, String>) (txt) -> serviceA.doWork(txt);
Function<String, Result<String>> stepB = (CheckedFunction<String, String>) (txt) -> serviceB.doWork(txt);

Result<String> a = stepA.apply(null);     // Failure(โ€ฆ)
Result<String> b = stepB.apply("Hello");  // Success("HELLO")

No nested try/catch, both calls execute, and each result carries its reason.

When not to use Checked*

Stack traces

Result.failure carries the exception’s message (or class name if the message is null), not the Throwable itself. If you need stack traces, log at the point of conversion:

CheckedFunction<String, Config> readConfig = path -> {
    try {
        return new Config(Files.readAllBytes(Paths.get(path)));
    } catch (IOException e) {
        log.error("config load failed for {}", path, e);
        throw e;   // still becomes Result.failure with e.getMessage()
    }
};