What is the difference between checked and unchecked exceptions?
In Java, all exceptions inherit from java.lang.Throwable. Exceptions are divided into two categories:
Junior Level
Basics
In Java, all exceptions inherit from java.lang.Throwable. Exceptions are divided into two categories:
Checked exceptions:
- Subclasses of
Exception(exceptRuntimeException) - The compiler requires you to handle them: either via
try-catchor viathrowsin the method signature - Examples:
IOException,SQLException,ClassNotFoundException
Unchecked exceptions:
- Subclasses of
RuntimeException - The compiler does not require handling
- Examples:
NullPointerException,IllegalArgumentException,IndexOutOfBoundsException
When to use
- Checked - for external recoverable failures (file not found, network unavailable)
- Unchecked - for programming errors (null, wrong argument) and business errors
// Checked - must handle or declare throws
public void readFile() throws IOException {
Files.readAllLines(Paths.get("file.txt"));
}
// Unchecked - compiler does not require handling
public void divide(int a, int b) {
if (b == 0) throw new ArithmeticException("Division by zero");
}
Middle Level
When NOT to use checked exceptions
- Business logic - checked exceptions break composition (need to declare throws in every method up the stack)
- REST controllers - Spring handles unchecked exceptions itself via @ExceptionHandler
- Stream API - lambdas do not support checked exceptions (Function<T,R> does not declare throws)
- CompletableFuture - exceptions are wrapped in CompletionException, checked lose their meaning
How it works
JVM does not enforce exception type checking at runtime - the checked/unchecked separation only works at compile time. In bytecode, all exceptions are handled the same way. The athrow instruction simply throws an object.
Exception Translation
Good practice - catching checked exceptions at layer boundaries and wrapping them in domain unchecked exceptions:
public class UserRepository {
public User findById(Long id) {
try {
return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?",
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name")), id);
} catch (DataAccessException e) {
throw new UserNotFoundException("User not found: " + id, e);
}
}
}
Multi-catch (Java 7+)
try {
// code
} catch (IOException | SQLException e) {
log.error("Resource error", e);
throw new ServiceException("Failed", e);
}
Why modern languages abandoned checked exceptions
Kotlin and Scala don’t have checked exceptions because:
- Signature pollution - adding
throwsrequires changing all methods up the stack - Incompatibility with FP - functional interfaces (Stream API, CompletableFuture) don’t support checked exceptions
Senior Level
Under the Hood: bytecode and JVM
The Exceptions attribute in the .class file stores information about checked exceptions, but JVM only uses it for verification, not for runtime control.
Sneaky Throws - bypassing the compiler
Since JVM does not check exception types, you can throw a checked exception without throws:
@SuppressWarnings("unchecked")
private static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
throw (E) e;
}
// Usage: sneakyThrow(new IOException()); - compiles without throws!
Lombok @SneakyThrows uses this same mechanism.
Type erasure: generic E is erased to Throwable at runtime. The compiler thinks the method throws E (a method parameter), while JVM throws the real type. That’s why throws is not needed in the signature.
This is dangerous: the caller doesn’t know the method can throw IOException. Use only inside frameworks, never in business logic.
Performance in Highload
fillInStackTrace() - the most expensive part of an exception. JVM walks the entire call stack. At 100k+ RPS, creating exceptions becomes a bottleneck.
Optimization: for signal exceptions, disable the stack trace:
public class FastException extends RuntimeException {
public FastException(String message) {
super(message, null, false, false); // writableStackTrace = false
}
}
This speeds up creation by 10-50x.
Architectural Choice
Checked exceptions are only good for external recoverable failures in low-level libraries:
InterruptedException- Java forces handling to not break thread management
Unchecked exceptions are preferable because:
- Clean business logic without
try-catchnoise - Centralized handling via
@ControllerAdvice - Compatibility with Reactive programming
Edge Cases
- Exception Translation at layer boundaries - DAO catches
SQLExceptionand wraps in domain unchecked exception - UncaughtExceptionHandler - always set a global handler for threads
- Stack Trace Filtering - in Highload, trim stack traces or use JSON format (Logstash)
Interview Cheat Sheet
Must know:
- Checked exceptions - subclasses of
Exception(exceptRuntimeException), compiler requires handling - Unchecked exceptions - subclasses of
RuntimeException, compiler does not require handling - Checked - for external recoverable failures (file, network, DB)
- Unchecked - for programming errors and business errors
- JVM does not check exception types at runtime - separation is only at compiler level
- Kotlin and Scala abandoned checked exceptions due to signature pollution and FP incompatibility
sneakyThrowallows bypassing compiler check via type erasure- For highload, disable stack trace (
writableStackTrace = false)
Frequent follow-up questions:
- Why did modern languages abandon checked exceptions? - Signature pollution, incompatibility with lambdas and Stream API
- What is exception translation? - Catching a checked exception at a layer boundary and wrapping in a domain unchecked exception
- How does sneakyThrow work? - Generic
E extends Throwableis erased toThrowable, JVM does not check the type - When is a checked exception justified? - For external recoverable failures in low-level libraries (IOException, InterruptedException)
Red flags (NOT to say):
- “I always use checked exceptions to force colleagues to handle errors” - this is an anti-pattern for business logic
- “JVM checks checked exceptions at runtime” - no, only the compiler
- “Kotlin doesn’t support exceptions at all” - Kotlin supports unchecked, it just removed checked
Related topics:
- [[What is a checked exception and when to use it]]
- [[What is an unchecked exception (Runtime Exception)]]
- [[Which exceptions must be handled]]
- [[What is the difference between Error and Exception]]
- [[What is Throwable]]