Which exceptions must be handled?
The Java compiler forces you to handle checked exceptions. These are all subclasses of Exception, except RuntimeException.
Junior Level
Checked exceptions - mandatory handling
The Java compiler forces you to handle checked exceptions. These are all subclasses of Exception, except RuntimeException.
Checked exceptions are a contract between a method and its caller. The method says: “I might encounter X, and you must be prepared for it.”
// Mandatory: either try-catch or throws
public void readFile() throws IOException {
Files.readAllLines(Paths.get("file.txt"));
}
// Or handle on the spot
public void safeRead() {
try {
Files.readAllLines(Paths.get("file.txt"));
} catch (IOException e) {
log.error("Failed to read file", e);
}
}
Common exceptions to handle
IOException- file and network operationsSQLException- database operationsClassNotFoundException- class loadingInterruptedException- multithreading
Unchecked exceptions - optional handling
NullPointerException, IllegalArgumentException and other RuntimeExceptions don’t need to be handled. They signal errors in code.
Basic rule
- Checked - external failures (files, network, DB) -> must handle
- Unchecked - programming errors -> must fix the code
Middle Level
Mandatory vs recommended handling
| Type | Obligation | Who requires |
|---|---|---|
| Checked | Must handle or declare throws | Compiler |
| Unchecked | Recommended to handle | Architecture/team |
| Error | Should not catch | JVM (recovery impossible) |
Compiler-level check
At the JVM level, there is no separation between checked and unchecked. The athrow instruction simply throws an object. The mandatory handling is only a javac check.
Exceptions attribute in class file
When you declare void method() throws IOException, an Exceptions attribute appears in the .class file. The compiler uses it for checking. Other languages (Kotlin, Groovy) ignore this attribute.
Sneaky Throws - bypassing the check
You can throw a checked exception without throws:
@SuppressWarnings("unchecked")
public static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
throw (E) e; // Type erasure deceives the compiler
}
// Lombok does this automatically
@SneakyThrows
public void readFile() {
// IOException without throws!
Files.readAllLines(Paths.get("file.txt"));
}
Exception Translation
The correct approach - catch checked exceptions at the layer boundary and translate to domain unchecked:
public interface Storage {
byte[] read(String path); // Does not depend on SQL
}
public class SqlStorage implements Storage {
public byte[] read(String path) {
try {
return jdbcTemplate.queryForObject(...);
} catch (SQLException e) {
throw new StorageException("Failed to read: " + path, e);
}
}
}
Senior Level
Functional programming and checked exceptions
Standard interfaces (Function, Predicate, Supplier) don’t support checked exceptions:
// Will not compile
stream.map(path -> Files.readString(path))
// Solution 1 - wrapper
stream.map(path -> {
try { return Files.readString(path); }
catch (IOException e) { throw new UncheckedIOException(e); }
})
// Solution 2 - Sneaky Throws
stream.map(path -> sneakyRead(path))
Performance
Never use checked exceptions for control flow. Throwing an exception - creating an object + walking the stack - is 100-1000x slower than returning Optional.
Input data validation
For invalid data, always use unchecked exceptions (IllegalArgumentException). This is a programmer error, not an external world issue.
InterruptedException is the exception that confirms the rule. This is not “control flow” in the usual sense, but a mechanism for cooperative thread cancellation. Its handling is part of the multithreading contract.
UncaughtExceptionHandler
If a checked exception “leaks” through sneaky throws and is not caught, it reaches the thread handler. Set a global handler:
Thread.setDefaultUncaughtExceptionHandler((thread, e) -> {
log.error("Uncaught exception in thread {}", thread.getName(), e);
});
Empty Catch Blocks - the worst crime
catch (IOException e) {} // Crime!
If you must handle but don’t know how - at least log:
catch (IOException e) {
log.error("IO error occurred", e);
throw new RuntimeException(e);
}
Diagnostics
- Static Analysis - SonarQube blocks empty catch blocks
- Micrometer - track exception counts through metrics
- Log Correlation - keep Trace ID for all error types
Interview Cheat Sheet
Must know:
- Checked exceptions - must handle or declare
throws(compiler requires) - Unchecked - recommended to handle at architecture level (not compiler)
- Error - should NOT be caught (JVM in unstable state)
- At JVM level there is no separation -
athrowinstruction throws everything the same way - Sneaky throws - bypass compiler check via type erasure
- Exception translation - catch checked at layer boundary -> translate to domain unchecked
- For invalid input data always use unchecked (
IllegalArgumentException) - Empty catch blocks - the worst crime: at minimum log
Frequent follow-up questions:
- How does JVM handle checked vs unchecked? - The same; separation is only in
javac - What are sneaky throws? - Bypassing check via
@SneakyThrows(Lombok) or generic erasure - Why is InterruptedException special? - It’s a cooperative thread cancellation mechanism, need to restore interrupt status
- What to do if you don’t know how to handle? - Log and rethrow as
RuntimeException
Red flags (NOT to say):
- “I leave empty catch blocks if I don’t know what to do” - at least log
- “I catch Error to prevent the app from crashing” - JVM is unstable, it’s meaningless
- “Checked exceptions are the same as unchecked” - no, the compiler only checks checked
Related topics:
- [[What is a checked exception and when to use it]]
- [[What is an unchecked exception (Runtime Exception)]]
- [[What is the difference between Error and Exception]]
- [[Why you should not swallow exceptions (catch empty)]]
- [[What are suppressed exceptions]]