Traps

94. Checked Exceptions in Streams and Lambdas

Functional interfaces in java.util.function do not declare checked exceptions, so lambdas that throw checked exceptions don't compile; the fix is a wrapper method, a sneaky-throw helper, or switching to unchecked exceptions.

Java 21 (core language)

Explanation

Function<T,R> declares R apply(T t) with no throws clause. If your lambda body throws a checked exception (IOException, SQLException, etc.), the compiler rejects it โ€” you're trying to throw a checked exception from a context that doesn't declare it.

Option 1 โ€” Wrapper method: write a private method that catches the checked exception and rethrows as unchecked. Clean, explicit, testable.

Option 2 โ€” Sneaky throw helper: use a utility that exploits generic type erasure to throw a checked exception without declaring it (used by Lombok's @SneakyThrows). Convenient but hides exception information from callers โ€” use carefully.

Option 3 โ€” Custom checked functional interface: define your own @FunctionalInterface ThrowingFunction<T,R> { R apply(T t) throws Exception; } and write an adapter that wraps it. Good for internal APIs.

Example

import java.io.*;
import java.util.List;
import java.util.stream.Collectors;

public class CheckedExceptionStreams {
    static String readFile(String path) throws IOException {
        return new String(new FileInputStream(path).readAllBytes());
    }

    // Option 1: wrapper method (safest)
    static String readFileUnchecked(String path) {
        try {
            return readFile(path);
        } catch (IOException e) {
            throw new RuntimeException("Failed to read: " + path, e);
        }
    }

    // Option 2: sneaky-throw helper (use sparingly)
    @SuppressWarnings("unchecked")
    static <E extends Throwable, T> T sneakyThrow(Throwable t) throws E {
        throw (E) t;
    }

    // Option 3: custom checked functional interface
    @FunctionalInterface
    interface ThrowingFunction<T, R> {
        R apply(T t) throws Exception;
    }

    static <T, R> java.util.function.Function<T, R> wrap(ThrowingFunction<T, R> fn) {
        return t -> {
            try { return fn.apply(t); }
            catch (Exception e) { throw new RuntimeException(e); }
        };
    }

    public static void main(String[] args) {
        List<String> paths = List.of("/tmp/a.txt", "/tmp/b.txt");

        // BAD: doesn't compile!
        // paths.stream().map(p -> readFile(p)).toList(); // error

        // Option 1: wrapper
        List<String> contents1 = paths.stream()
            .map(CheckedExceptionStreams::readFileUnchecked)
            .collect(Collectors.toList());
        System.out.println("Option 1 done (files may not exist โ€” RuntimeException expected)");

        // Option 3: wrap() adapter
        List<String> contents3 = paths.stream()
            .map(wrap(CheckedExceptionStreams::readFile))
            .collect(Collectors.toList());
    }
}

Sources