Working with Exception Handling Advanced Patterns
1. Creating Custom Exception Hierarchies
| Type | Use |
|---|---|
| Checked | Recoverable, caller must handle |
| Unchecked (RuntimeException) | Programming errors, optional handling |
| Error | JVM-level; do not catch |
| Hierarchy | Domain root → specific subclasses |
Example: Domain hierarchy
public abstract class DomainException extends RuntimeException {
protected DomainException(String msg, Throwable cause) { super(msg, cause); }
}
public class NotFoundException extends DomainException {
public NotFoundException(String id) { super("not found: " + id, null); }
}
2. Using Multi-Catch Blocks (Java 7+)
| Form | Detail |
|---|---|
catch (A | B e) | Single handler |
| e is | effectively final, common-supertype |
| Restriction | No subtype + supertype together |
3. Using Try-with-Resources (AutoCloseable)
| Aspect | Detail |
|---|---|
| Resource | Implements AutoCloseable |
| Multiple | Separated by ; |
| Close order | Reverse declaration |
| Effectively final | Java 9+: pre-declared resource var allowed |
| Suppressed | Exceptions in close added as suppressed |
4. Handling Suppressed Exceptions (getSuppressed)
| API | Use |
|---|---|
e.getSuppressed() | Throwable[] |
e.addSuppressed(t) | Manual add |
| Disabled | Constructor with enableSuppression=false |
5. Understanding Exception Translation
| Pattern | Use |
|---|---|
| Wrap low-level | throw new ServiceException("...", sql) |
| Preserve cause | Always pass original |
| Layer boundary | Translate at API layer (DAO → service) |
6. Using Sneaky Throws Pattern
Example: Sneaky throw
@SuppressWarnings("unchecked")
static <E extends Throwable> void sneakyThrow(Throwable t) throws E { throw (E) t; }
// Usage: sneakyThrow(new IOException("oops"));
| Pros | Cons |
|---|---|
| No checked declaration | Hides exception flow |
| Lambda-friendly | Surprising for callers |
7. Wrapping Checked Exceptions in Lambdas
Example: Wrapper
public static <T, R> Function<T, R> rethrow(ThrowingFunction<T, R, ?> fn) {
return t -> { try { return fn.apply(t); }
catch (Exception e) { sneakyThrow(e); return null; } };
}
| Approach | Trade-off |
|---|---|
| Throwing FI | Type-safe |
| Wrapper utility | Less ceremony |
| Sneaky | Hides checked |
8. Creating Exception Handling Utilities
| Utility | Use |
|---|---|
Try.of(supplier) | Vavr-style result |
| Retry with backoff | Resilience4j |
| Circuit breaker | Resilience4j / Failsafe |
Custom uncheck | Lambda wrapping |
9. Using Result/Either Types for Functional Error Handling
Example: Sealed Result
public sealed interface Result<T, E> {
record Ok<T, E>(T value) implements Result<T, E> { }
record Err<T, E>(E error) implements Result<T, E> { }
}
| Benefit | Detail |
|---|---|
| Explicit failure | Type signature documents |
| Composable | map / flatMap |
| No exceptions | For expected errors |
10. Understanding Stack Trace Manipulation
| API | Use |
|---|---|
setStackTrace(StackTraceElement[]) | Override trace |
fillInStackTrace() | Re-capture |
| Disable | Constructor flag writableStackTrace=false |
| StackWalker | Modern stack inspection (Java 9+) |
11. Understanding Exception Performance Implications
| Cost | Mitigation |
|---|---|
| Stack capture | Disable for control-flow exceptions |
| Throw in hot path | Use Result type instead |
| Catch overhead | Negligible if not thrown |
| JIT | Optimizes uncatch'd happy paths |
12. Using Chained Exceptions (initCause)
| API | Use |
|---|---|
new X(msg, cause) | Preferred constructor |
initCause(t) | For exceptions w/o cause ctor |
getCause() | Walk chain |