Question 1 · Section 7

What is the difference between checked and unchecked exceptions?

In Java, all exceptions inherit from java.lang.Throwable. Exceptions are divided into two categories:

Language versions: English Russian Ukrainian

Junior Level

Basics

In Java, all exceptions inherit from java.lang.Throwable. Exceptions are divided into two categories:

Checked exceptions:

  • Subclasses of Exception (except RuntimeException)
  • The compiler requires you to handle them: either via try-catch or via throws in 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

  1. Business logic - checked exceptions break composition (need to declare throws in every method up the stack)
  2. REST controllers - Spring handles unchecked exceptions itself via @ExceptionHandler
  3. Stream API - lambdas do not support checked exceptions (Function<T,R> does not declare throws)
  4. 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:

  1. Signature pollution - adding throws requires changing all methods up the stack
  2. 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-catch noise
  • Centralized handling via @ControllerAdvice
  • Compatibility with Reactive programming

Edge Cases

  • Exception Translation at layer boundaries - DAO catches SQLException and 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 (except RuntimeException), 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
  • sneakyThrow allows 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 Throwable is erased to Throwable, 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]]