Question 2 · Section 7

What is a checked exception and when to use it?

Use a checked exception if:

Language versions: English Russian Ukrainian

Junior Level

Definition

Checked exception - an exception type that the Java compiler requires you to handle.

Why: Java designers wanted to force developers to explicitly decide how to react to predictable external failures - a file might be missing, the network might drop. Without checked exceptions, these situations are easy to miss. If a method can throw a checked exception, you must:

  1. Wrap the call in try-catch
  2. Or add throws to the method signature

Examples

// Compiler will force handling of IOException
public String readFile() throws IOException {
    return Files.readString(Paths.get("data.txt"));
}

// Must handle
public void safeRead() {
    try {
        Files.readString(Paths.get("data.txt"));
    } catch (IOException e) {
        System.err.println("Failed to read: " + e.getMessage());
    }
}

When to use

Use a checked exception if:

  • Failure is expected - file might be missing, network might drop
  • Failure is recoverable - can retry, try another path
  • It is critical - cannot simply “forget” about this situation

Common checked exceptions

  • IOException - I/O problems
  • SQLException - database errors
  • ClassNotFoundException - class not found
  • InterruptedException - thread interrupted

Middle Level

What to choose: throws or try-catch?

  • throws - when the method cannot decide how to handle. Let the caller decide.
  • try-catch - when you can handle it on the spot: log, return a default value, retry.

Compilation mechanism

The javac compiler checks the call graph. If somewhere in the chain a method with a checked exception is called and it is not handled, compilation fails.

Exception Translation

In large projects with multi-layer architecture, propagating checked exceptions breaks layer encapsulation. In smaller projects, it is acceptable to propagate them upward.

public Order createOrder(Order order) {
    try {
        return orderRepository.save(order);
    } catch (SQLException e) {
        throw new OrderStorageException("Failed to save order", e);
    }
}

OrderStorageException is a runtime exception. Business logic does not depend on JDBC.

Three conditions for using checked exceptions

  1. Failure is expected - not a code bug, but an external circumstance
  2. Failure is recoverable - calling code can do something about it
  3. Critical for business - cannot be ignored

Lambda and checked exceptions

Functional interfaces do not support checked exceptions:

// Will not compile!
list.stream().map(path -> Files.readString(path))
// Why: Function<T,R> interface does not declare throws in its apply() signature.
// So the lambda cannot propagate a checked exception - compiler won't allow it.

// Solution - wrapper
list.stream().map(path -> {
    try {
        return Files.readString(path);
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    }
})

Senior Level

Under the Hood: Exceptions attribute

In the .class file, information about throws is stored in the method’s Exceptions attribute. JVM does not use it for runtime control - only for the compiler verifier.

Binary Compatibility

Adding a checked exception to a method in an existing library breaks binary compatibility. All clients will stop compiling.

Removing an exception - binary compatible, but leaves excess code in clients.

Overriding and LSP

The Liskov Substitution Principle dictates the rules:

  • A subclass method can not throw the parent’s exceptions
  • Can throw subtypes of the parent’s exceptions
  • Cannot throw new checked exceptions

Performance

A checked exception is still a Throwable with expensive fillInStackTrace(). For frequent “expected” situations, it’s better to return Optional<T> or Result objects.

Edge Cases

  • InterruptedException - a “good” checked exception. When caught, restore the status: Thread.currentThread().interrupt()
  • Generic Throws - Java allows public <E extends Exception> void doWork() throws E
  • throws Exception - never write this in a public API

Monitoring

Don’t throw Exception or Throwable. Create your own types for clear error separation in Micrometer/Prometheus.


Interview Cheat Sheet

Must know:

  • Checked exception - compiler requires handling (try-catch or throws)
  • Use if failure is expected, recoverable, and critical for business
  • Common: IOException, SQLException, ClassNotFoundException, InterruptedException
  • throws - when the method cannot decide how to handle; try-catch - when you can
  • Exception Translation - wrapping checked in domain unchecked for architectural layers
  • Lambda does not support checked exceptions - needs wrapper in UncheckedIOException
  • Adding a checked exception breaks binary compatibility of a library
  • When overriding, a subclass method cannot add new checked exceptions (LSP)

Frequent follow-up questions:

  • Why don’t checked exceptions work with lambdas? - Functional interfaces (Function, Predicate) don’t declare throws in their signature
  • What happens when adding a checked exception to a public library API? - All clients will stop compiling - breaks binary compatibility
  • How to properly handle InterruptedException? - Catch and restore status: Thread.currentThread().interrupt()
  • When is a checked exception better than unchecked? - When the caller can do something about it (retry, choose an alternative path)

Red flags (NOT to say):

  • “I write throws Exception everywhere to avoid thinking” - this breaks the method contract
  • “Checked exceptions are a JVM problem, not an application one” - no, these are application errors
  • “I catch all checked exceptions and do nothing” - silently swallowing errors

Related topics:

  • [[What is the difference between checked and unchecked exceptions]]
  • [[What is an unchecked exception (Runtime Exception)]]
  • [[Which exceptions must be handled]]
  • [[What is at the top of the exception hierarchy]]
  • [[What is Throwable]]