Definition
- Function, Consumer, Predicate do not declare
throws Exception. - Checked exception in lambda will not compile.
- Direct fix: try-catch inside the lambda, rethrow as
RuntimeException. - Cleaner fix: a reusable
unchecked(CheckedFunction)wrapper. - For expected failures: return
OptionalorEitherinstead of throwing.
At National Bank of India, the stream's forms only accept answers that never throw a checked error. Priya must either handle the error on the spot inside the form (inline try-catch) or run it through a universal adapter that converts checked errors to unchecked ones.
Understanding the Concepts
Why do checked exceptions not work in lambdas?
The functional interfaces used by streams (Function, Consumer, Predicate) have methods that do not declare any checked exception. If your lambda body calls a method that throws a checked exception, the compiler rejects it because the functional interface cannot propagate it.
How do you handle it with inline try-catch?
package com.bank.service;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class InlineTryCatch {
public static void main(String[] args) {
List<String> dateStrings = List.of("2024-01-15", "2024-02-28", "invalid");
List<Date> dates = dateStrings.stream()
.map(s -> {
try {
return new SimpleDateFormat("yyyy-MM-dd").parse(s);
} catch (ParseException e) {
throw new RuntimeException("Failed to parse: " + s, e);
}
})
.toList();
}
}
This works but is verbose when repeated.
How do you create a reusable wrapper?
package com.bank.util;
import java.util.function.Function;
@FunctionalInterface
public interface CheckedFunction<T, R> {
R apply(T t) throws Exception;
}
public class StreamUtils {
public static <T, R> Function<T, R> unchecked(CheckedFunction<T, R> f) {
return t -> {
try {
return f.apply(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}
Usage:
import static com.bank.util.StreamUtils.unchecked;
List<Date> dates = dateStrings.stream()
.map(unchecked(s -> new SimpleDateFormat("yyyy-MM-dd").parse(s)))
.toList();
// Clean, readable, checked exception handled in one place
How is inline try-catch different from the wrapper?
| Approach | Pros | Cons |
|---|---|---|
| Inline try-catch | No extra class | Verbose, repeated code |
| unchecked() wrapper | Clean call sites, reusable | One extra interface + utility class |
| Return Optional | No exception at all | Changes return type, needs downstream handling |
What about expected failures?
When failure is expected (malformed records in a batch), prefer returning Optional or an Either-like result instead of throwing.
List<Optional<Date>> results = dateStrings.stream()
.map(s -> {
try {
return Optional.of(new SimpleDateFormat("yyyy-MM-dd").parse(s));
} catch (ParseException e) {
return Optional.<Date>empty();
}
})
.toList();
// Filter to keep only successful parses
List<Date> successfulDates = results.stream()
.flatMap(Optional::stream)
.toList();
What are common mistakes?
Mistake: Losing the original exception cause.
// WRONG: cause is lost
catch (Exception e) {
throw new RuntimeException("parse failed"); // no cause!
}
// CORRECT: preserve the cause
catch (Exception e) {
throw new RuntimeException("parse failed", e); // cause preserved
}
What about wrappers for Consumer and Supplier?
Make analogous wrappers for other functional interfaces.
@FunctionalInterface
interface CheckedConsumer<T> { void accept(T t) throws Exception; }
static <T> java.util.function.Consumer<T> unchecked(CheckedConsumer<T> c) {
return t -> {
try { c.accept(t); }
catch (Exception e) { throw new RuntimeException(e); }
};
}
What should a senior engineer know?
Choosing between wrap-and-throw and result-objects depends on whether failure is exceptional or expected. For truly exceptional failures, the unchecked wrapper is clean. For expected per-element failures in batch processing, returning Optional or Either preserves partial results.
What are common interview follow-up questions?
- "Why can you not declare throws on a lambda?" The functional interface's method signature does not allow it.
- "When should you throw vs return Optional?" Throw for exceptional failures; return Optional for expected/recoverable ones.
- "How do you handle IOException specifically?" Use
UncheckedIOExceptionfor the rethrow.
Visual Overview
Lambda calls method that throws CheckedException:
┌──────────────────────────────────────────┐
│ map(s -> riskyParse(s)) │
│ ← compile error: unhandled exception │
└──────────────────────────────────────────┘
Fix 1: inline try-catch
┌──────────────────────────────────────────┐
│ map(s -> { │
│ try { return parse(s); } │
│ catch (E e) { throw new RTE(e); } │
│ }) │
└──────────────────────────────────────────┘
Fix 2: unchecked() wrapper
┌──────────────────────────────────────────┐
│ map(unchecked(s -> parse(s))) │
│ ← clean, reusable │
└──────────────────────────────────────────┘
Key Takeaway
Stream functional interfaces like Function and Consumer do not declare checked exceptions, so a lambda that throws a checked exception will not compile. The direct fix is a try-catch inside the lambda that rethrows as an unchecked exception. The cleaner fix is a reusable unchecked() wrapper that centralizes the conversion so every call site stays clean.
At the National Bank of India, Rajesh created a StreamUtils.unchecked() wrapper for the team, covering Function, Consumer, and Supplier. For expected failures like malformed records in a batch, he returns Optional instead of throwing, so one bad record does not abort the entire pipeline.
The choice between wrap-and-throw and result-objects depends on whether failure is exceptional or expected. Always preserve the original cause when rethrowing so the stack trace remains intact.
Remember one sentence: checked exceptions in lambdas require a try-catch or a reusable unchecked() wrapper because stream functional interfaces do not declare throws.
---