Definition
- CheckedFunction
: a @FunctionalInterfacewhoseapplydeclaresthrows Exception. - unchecked() method: takes a
CheckedFunctionand returns a standardFunction. - Internal try-catch: catches
Exceptionand rethrows asRuntimeException(cause). - Type-safe generics: works for any input T and output R.
- Extend: make analogous wrappers for Consumer, Supplier, Runnable.
Priya at National Bank of India builds a universal adapter: feed it any form that might throw a checked error, and it hands back a form the stream accepts, converting any error to an unchecked one. One adapter works for all form types.
Understanding the Concepts
How do you implement the CheckedFunction interface?
package com.bank.util;
@FunctionalInterface
public interface CheckedFunction<T, R> {
R apply(T t) throws Exception;
}
This interface is identical to Function except that its apply method declares throws Exception, so a lambda that throws a checked exception can satisfy it.
How do you implement the unchecked() wrapper?
package com.bank.util;
import java.util.function.Function;
public final class StreamUtils {
private StreamUtils() {} // utility class
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); // preserves cause
}
};
}
}
How do you use it in a stream?
package com.bank.service;
import static com.bank.util.StreamUtils.unchecked;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class UncheckedUsage {
public static void main(String[] args) {
List<String> dateStrings = List.of("2024-01-15", "2024-06-30");
List<Date> dates = dateStrings.stream()
.map(unchecked(s -> new SimpleDateFormat("yyyy-MM-dd").parse(s)))
.toList();
dates.forEach(d -> System.out.println("Parsed: " + d));
}
}
Parsed: Mon Jan 15 00:00:00 IST 2024
Parsed: Sun Jun 30 00:00:00 IST 2024
How do you extend it for Consumer and Supplier?
@FunctionalInterface
interface CheckedConsumer<T> {
void accept(T t) throws Exception;
}
@FunctionalInterface
interface CheckedSupplier<T> {
T get() throws Exception;
}
public static <T> java.util.function.Consumer<T> unchecked(CheckedConsumer<T> c) {
return t -> {
try { c.accept(t); }
catch (Exception e) { throw new RuntimeException(e); }
};
}
public static <T> java.util.function.Supplier<T> unchecked(CheckedSupplier<T> s) {
return () -> {
try { return s.get(); }
catch (Exception e) { throw new RuntimeException(e); }
};
}
What about specific exception types?
// For IOException, use UncheckedIOException for clarity
import java.io.UncheckedIOException;
import java.io.IOException;
public static <T, R> Function<T, R> uncheckedIO(CheckedFunction<T, R> f) {
return t -> {
try {
return f.apply(t);
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
What are common mistakes?
Mistake: Not preserving the cause in the rethrown exception.
// WRONG: cause lost, stack trace incomplete
catch (Exception e) {
throw new RuntimeException("failed");
}
// CORRECT: cause preserved
catch (Exception e) {
throw new RuntimeException(e);
}
How is this different from Lombok's @SneakyThrows?
| Approach | Mechanism | Safety |
|---|---|---|
| unchecked() wrapper | Catches and rethrows as unchecked | Explicit, type-safe |
| @SneakyThrows (Lombok) | Tricks the compiler into allowing checked throws | Implicit, bypasses type system |
| Inline try-catch | Manual in every lambda | Verbose but explicit |
What should a senior engineer know?
The wrapper centralizes the checked-to-unchecked conversion so it is written once and used everywhere. Always preserve the cause, and prefer specific unchecked types (UncheckedIOException) over bare RuntimeException. For expected failures, prefer result-objects over exceptions.
What are common interview follow-up questions?
- "Why not use @SneakyThrows?" It bypasses the type system and surprises callers who do not expect checked exceptions.
- "Can you make it work with Predicate?" Yes, create a CheckedPredicate interface and an unchecked wrapper.
- "Why preserve the cause?" So the original exception and stack trace are visible in logs and debuggers.
Visual Overview
CheckedFunction<T, R> Function<T, R>
┌────────────────────┐ unchecked() ┌────────────────────┐
│ R apply(T) throws │ ───────────────► │ R apply(T) │
│ Exception │ │ { try { f(t) } │
└────────────────────┘ │ catch → RTE } │
└────────────────────┘
Usage:
stream.map(unchecked(s -> riskyParse(s))) ← clean, reusable
Key Takeaway
The utility has two parts. First, a CheckedFunction functional interface whose apply declares throws Exception, so a checked-throwing lambda can satisfy it. Second, a generic unchecked() helper that takes a CheckedFunction and returns a standard Function by wrapping the call in try-catch and rethrowing as unchecked.
At the National Bank of India, Rajesh created one set of wrappers covering Function, Consumer, and Supplier, giving the team a clean, reusable adapter instead of repeating try-catch in every pipeline. The generics keep it type-safe for any input and output.
The key practices are: preserve the cause in the rethrown exception so the stack trace is intact, prefer specific unchecked types like UncheckedIOException where applicable, and remember that for expected recoverable failures, returning result-objects (Optional or Either) is better than throwing.
Remember one sentence: unchecked() wraps a checked-throwing lambda into a stream-compatible Function by catching and rethrowing as unchecked, centralizing the conversion in one reusable place.
---