Working with checked exceptions
Lift throwing JDK methods into Result-returning functions, drop the try/catch ladder, and use the result in Streams.
May 17, 2026
Half the code in a typical Java service exists to satisfy checked exceptions you don’t actually want to handle there. try/catch wraps Integer.parseInt, Files.readAllBytes, Class.forName โ every one of them shouting “declare me or wrap me.” This tutorial shows how CheckedFunction and its siblings reduce that noise to a single cast, with the failure travelling forward as a Result<T>.
The starting position
public interface Service {
String doWork(String txt) throws Exception;
}
Service serviceA = txt -> txt.toUpperCase() + "-A";
Service serviceB = txt -> txt.toUpperCase() + "-B";
try {
String a = serviceA.doWork("Hello");
} catch (Exception e) {
e.printStackTrace();
}
try {
String b = serviceB.doWork(null);
} catch (Exception e) {
e.printStackTrace();
}
Two try/catch blocks for two calls. No way to compose. No way to thread the result through a stream.
Lift to Result with one cast
CheckedFunction<T, R> extends Function<T, Result<R>> and provides a default apply that wraps your throwing implementation in a try/catch, producing Result.success(value) or Result.failure(message):
import com.svenruppert.functional.functions.CheckedFunction;
import com.svenruppert.functional.model.Result;
Function<String, Result<String>> safeA = (CheckedFunction<String, String>) serviceA::doWork;
Function<String, Result<String>> safeB = (CheckedFunction<String, String>) serviceB::doWork;
Result<String> a = safeA.apply("Hello"); // Success("HELLO-A")
Result<String> b = safeB.apply(null); // Failure("Cannot invoke ...")
Two casts, zero try/catch. Both calls run unconditionally โ no manual sequencing required.
A tryIt helper
If the cast bothers you, hide it in a one-line helper:
public static <T, R> Function<T, Result<R>> tryIt(CheckedFunction<T, R> f) { return f; }
Function<String, Result<String>> safeA = tryIt(serviceA::doWork);
Function<String, Result<String>> safeB = tryIt(serviceB::doWork);
Or as a function value, if you prefer composing:
public static <T, R> Function<CheckedFunction<T, R>, Function<T, Result<R>>>
tryIt = f -> f;
Function<String, Result<String>> safeA = tryIt.apply(serviceA::doWork);
Pick whichever your codebase already leans toward.
In a Stream
The biggest payoff is inside a Stream pipeline. Without CheckedFunction, you cannot put Integer::valueOf into a .map() call because it throws โ you have to wrap, log, sentinel-out, or burn 10 lines on a static helper.
import java.util.stream.Stream;
Stream.of("1", "2", "Hi", "3")
.map((CheckedFunction<String, Integer>) Integer::valueOf) // Stream<Result<Integer>>
.flatMap(Result::stream) // Stream<Integer>
.reduce(Integer::sum)
.ifPresent(System.out::println); // 6
Result::stream emits zero or one element, so the failed parse ("Hi") silently drops out of the pipeline. The rest of the pipeline keeps running, summing 1 + 2 + 3.
If you want to keep failures and log them:
Map<Boolean, List<Result<Integer>>> parts = Stream.of("1", "2", "Hi", "3")
.map((CheckedFunction<String, Integer>) Integer::valueOf)
.collect(Collectors.partitioningBy(Result::isPresent));
parts.get(true ).forEach(r -> r.ifPresent(System.out::println));
parts.get(false).forEach(r -> r.ifFailed (e -> log.warn(e)));
The whole family
CheckedFunction has matching siblings for every common arity:
| Interface | Throwing method |
|---|---|
CheckedSupplier<T> | T getWithException() throws Exception |
CheckedFunction<T, R> | R applyWithException(T) throws Exception |
CheckedBiFunction<T1, T2, R> | R applyWithException(T1, T2) throws Exception |
CheckedTriFunction<T1, T2, T3, R> | R applyWithException(T1, T2, T3) throws Exception |
CheckedConsumer<T> | inherited from CheckedFunction<T, Void> |
CheckedExecutor | void applyWithException() throws Exception |
CheckedPredicate<T> | boolean testWithExceptions(T) throws Exception |
All return Result (except CheckedPredicate, which returns false on a throw โ appropriate for a predicate).
Stack traces
Result.failure carries the exception’s getMessage(), falling back to the simple class name if the message is null. It does not carry the Throwable itself. If you need the stack trace โ for an error report, for a debugger โ log it at the point of conversion:
CheckedFunction<String, Config> loadConfig = path -> {
try {
return new Config(Files.readAllBytes(Paths.get(path)));
} catch (IOException e) {
log.error("config load failed for {}", path, e); // full trace logged
throw e; // becomes Result.failure(e.getMessage())
}
};
The lifecycle stays: log details at the boundary, carry a human-readable reason forward.
Recap
CheckedFunction<T, R>extendsFunction<T, Result<R>>โ cast a throwing method reference to it and you get a non-throwing function back.tryIt(method::ref)is a one-line helper that hides the cast.- Inside streams,
.map(checkedFn).flatMap(Result::stream)is the canonical pattern. - Every common arity has a
Checked*variant โSupplier,BiFunction,TriFunction,Consumer,Executor,Predicate. - Stack traces don’t ride along โ log them at the conversion point, let the message travel.
Next
Checked FunctionsAPI reference.- Error handling with
Resultโ whatCheckedFunctionproduces. - Streams meet functions โ once your stream stages return
Result, the whole pipeline gets cleaner.