Checked Interfaces

Runnable example: CheckedInterfacesSample.java

The problem: checked exceptions in lambdas

Java’s standard functional interfaces — Function, Supplier, Consumer, Runnable — do not declare checked exceptions. Any lambda that calls a method throwing a checked exception must wrap it in a try/catch block, converting it to an unchecked exception inline. This produces noise that obscures intent.

// Standard Function<T,R> does not declare checked exceptions.
// Every checked call inside a lambda requires manual wrapping.
Function<Path, String> reader = path -> {
try {
return Files.readString(path); // IOException is checked
} catch (IOException e) {
throw new RuntimeException(e); // boilerplate every time
}
};
// With CheckedFunction the method reference is used directly:
CheckedFunction<Path, String> reader = Files::readString; // clean

dmx-fun provides Checked* variants of the four most common functional interfaces so that throwing method references can be used directly.

Checked functional interfaces

InterfaceChecked equivalent ofSignature
CheckedFunction<T, R>Function<T, R>R apply(T t) throws Exception
CheckedSupplier<T>Supplier<T>T get() throws Exception
CheckedConsumer<T>Consumer<T>void accept(T t) throws Exception
CheckedRunnableRunnablevoid run() throws Exception

All four are @FunctionalInterface and @NullMarked.

// CheckedFunction<T, R> — like Function<T,R> but may throw Exception
CheckedFunction<Path, String> readFile = Files::readString;
CheckedFunction<String, Class<?>> loader = Class::forName;
// CheckedSupplier<T> — like Supplier<T> but may throw Exception
CheckedSupplier<Connection> openConn = () -> dataSource.getConnection();
// CheckedConsumer<T> — like Consumer<T> but may throw Exception
CheckedConsumer<OutputStream> closeStream = Closeable::close;
// CheckedRunnable — like Runnable but may throw Exception
CheckedRunnable initDb = () -> flyway.migrate();

Integrating with Try

CheckedSupplier<T> is the natural input for Try.of() — the supplier is executed and any thrown exception is captured as a Failure without any manual wrapping. CheckedRunnable feeds Try.run() for side-effecting operations that produce no value.

// Try.of(CheckedSupplier<V>) — captures any exception as Failure
Try<String> content = Try.of(() -> Files.readString(path));
// Method reference — Files::readString matches CheckedFunction<Path,String>
// but also satisfies CheckedSupplier<String> when path is already in scope
Try<String> content2 = Try.of(() -> Files.readString(Paths.get("config.txt")));
// Try.run(CheckedRunnable) — for side-effecting operations that return nothing
Try<Void> migration = Try.run(() -> flyway.migrate());
// CheckedFunction used with Try.traverse — apply to every element,
// collecting results or accumulating the first failure
List<Path> paths = List.of(Paths.get("a.txt"), Paths.get("b.txt"));
Try<List<String>> allContents =
Try.traverse(paths, path -> Try.of(() -> Files.readString(path)));
// Success([...]) if all reads succeed, Failure(IOException) on first error

Higher-arity function interfaces

The standard library stops at BiFunction<A,B,R>. dmx-fun extends this with:

InterfaceArgumentsUsed by
TriFunction<A, B, C, R>3Tuple3.map(TriFunction)
QuadFunction<A, B, C, D, R>4Tuple4.map(QuadFunction)

Both follow the same @FunctionalInterface + @NullMarked contract as the standard library interfaces.

// TriFunction<A, B, C, R> — three arguments, one result
TriFunction<String, Integer, Boolean, String> describe =
(name, age, active) -> name + " (age " + age + ", active=" + active + ")";
String label = describe.apply("Alice", 30, true);
// "Alice (age 30, active=true)"
// QuadFunction<A, B, C, D, R> — four arguments, one result
QuadFunction<String, Integer, Boolean, Double, String> summarize =
(name, age, active, score) ->
name + " | " + age + "y | " + (active ? "active" : "inactive") + " | " + score;
String summary = summarize.apply("Alice", 30, true, 9.5);
// "Alice | 30y | active | 9.5"
// Both are used by Tuple3.map and Tuple4.map
String t3result = Tuple3.of("Alice", 30, true).map(describe);
String t4result = Tuple4.of("Alice", 30, true, 9.5).map(summarize);

When to use each

ScenarioInterface
Pass a throwing method reference to a functional APICheckedFunction / CheckedSupplier
Wrap a throwing void action (e.g. DB migration, file write)CheckedRunnable
Side-effecting loop body that throwsCheckedConsumer
Feed a throwing supplier into Try.of()CheckedSupplier
Collapse a Tuple3 into a single valueTriFunction
Collapse a Tuple4 into a single valueQuadFunction
Combine more than two values in a Validated.combine chainTriFunction / QuadFunction

Real-world example

Reading and parsing a set of report files using CheckedFunction, Try.traverse, and TriFunction to keep the pipeline clean and exception-safe.

// Read a set of report files and parse each one, collecting results or failures.
// CheckedFunction keeps the lambda clean; Try.traverse accumulates the outcome.
List<Path> reportPaths = List.of(
Paths.get("reports/q1.csv"),
Paths.get("reports/q2.csv"),
Paths.get("reports/q3.csv")
);
// Step 1 — read each file; any IOException becomes a Failure
Try<List<String>> rawContents =
Try.traverse(reportPaths, path -> Try.of(() -> Files.readString(path)));
// Step 2 — parse each raw string into a Report; parse errors become Failures
Try<List<Report>> reports = rawContents.flatMap(contents ->
Try.traverse(contents, raw -> Try.of(() -> Report.parse(raw))));
// Step 3 — generate the combined output or log the first error
switch (reports) {
case Try.Success<List<Report>>(var list) ->
exportCombinedReport(list);
case Try.Failure<List<Report>>(var ex) ->
log.error("Report generation failed: {}", ex.getMessage(), ex);
}
// TriFunction used to merge three quarterly totals into a summary record
TriFunction<BigDecimal, BigDecimal, BigDecimal, YearlySummary> toSummary =
(q1, q2, q3) -> new YearlySummary(q1.add(q2).add(q3));
YearlySummary yearly = Tuple3.of(q1Total, q2Total, q3Total).map(toSummary);