Checked Exceptions in Lambdas

Streams · lesson 34 of 42 · 4 min read

Why a lambda cannot throw a checked exception, and the three honest ways around it.

Open this lesson in the learning hub

Key points

  • Function, Predicate and the rest declare no checked exceptions, so a lambda body cannot throw one.
  • Option 1: catch inside the lambda and return a fallback. Simple, but the failure quietly disappears.
  • Option 2: wrap it in an unchecked exception. The stream stops on the first bad element, which is often right.
  • Option 3: return a small result record, then partitioningBy the successes from the failures.
  • A sneaky-throw helper compiles, but callers get an exception no signature ever mentioned. Avoid it.
  • Decide up front whether one bad element should stop the batch or just be reported at the end.

Example

import java.util.*;
import java.util.stream.*;

public class Main {

    // Pretend this reads a file or calls a service: it declares a checked exception.
    static int parse(String s) throws Exception {
        for (char c : s.toCharArray()) {
            if (c < '0' || c > '9') {
                throw new Exception("not a number: " + s);
            }
        }
        return Integer.parseInt(s);
    }

    record Result(String input, Integer value, String error) {
        boolean ok() {
            return error == null;
        }
    }

    public static void main(String[] args) {
        List<String> raw = List.of("10", "oops", "32");

        // 1. Swallow it and fall back. Cheap, but the failure disappears.
        System.out.println("fallback : " + raw.stream().map(s -> {
            try {
                return parse(s);
            } catch (Exception e) {
                return -1;
            }
        }).toList());

        // 2. Rethrow unchecked. The whole stream stops on the first bad element.
        try {
            raw.stream().mapToInt(s -> {
                try {
                    return parse(s);
                } catch (Exception e) {
                    throw new IllegalStateException(e.getMessage(), e);
                }
            }).sum();
        } catch (IllegalStateException e) {
            System.out.println("rethrown : " + e.getMessage());
        }

        // 3. Carry the outcome as data, then split successes from failures.
        Map<Boolean, List<Result>> split = raw.stream().map(s -> {
            try {
                return new Result(s, parse(s), null);
            } catch (Exception e) {
                return new Result(s, null, e.getMessage());
            }
        }).collect(Collectors.partitioningBy(Result::ok));

        System.out.println("ok       : " + split.get(true).stream().map(Result::value).toList());
        System.out.println("failed   : " + split.get(false).stream().map(Result::error).toList());
    }
}

If the failure must not stop the batch, turn it into data and collect it.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Streams course, and every lesson in it is listed on the Streams contents page.