Java

Checked exceptions, unchecked exceptions, and try-with-resources

The one real difference between the two families, and the resource leak try-with-resources exists to prevent.

After this lesson you can

  • Tell a checked exception from an unchecked one
  • Say why the compiler forces you to handle one but not the other
  • Use try-with-resources instead of a hand-written finally block

Every Throwable is either checked or unchecked, and the difference is entirely about the compiler, not about how serious the problem is.

public String readFile(String path) throws IOException {   // checked: must declare
    return Files.readString(Path.of(path));
}

public int divide(int a, int b) {                            // unchecked: no declaration needed
    return a / b;   // may throw ArithmeticException, uncaught
}

IOException extends Exception — checked. The compiler requires every method that can throw one to either catch it or declare it with throws, and every caller is forced to deal with that, one way or another. ArithmeticException, NullPointerException, and IllegalArgumentException all extend RuntimeException — unchecked. Nothing forces a caller to acknowledge them; they propagate silently until something up the stack catches them, or nothing does and the program terminates.

The convention: checked exceptions for conditions a well-written caller can reasonably recover from — a missing file, a failed network call. Unchecked exceptions for programmer errors — a null that should never have been null, an index that was never in range — where the fix is a code change, not a recovery path.

Try it

The most specific catch clause winsjava-21
public class Solution {    public static String describe() {        try {            int[] nums = {1, 2, 3};            return "value=" + nums[5];        } catch (ArrayIndexOutOfBoundsException e) {            return "array: " + e.getMessage();        } catch (RuntimeException e) {            return "runtime: " + e.getMessage();        }    }}
What to look for

Catch the most specific type first

try {
    process(data);
} catch (FileNotFoundException e) {   // a subclass of IOException
    // ...
} catch (IOException e) {              // the broader type
    // ...
}

A catch block matches the first clause whose type the thrown exception is an instance of, top to bottom. Putting the broader IOException first would catch everything there, and the compiler refuses to compile a narrower catch clause that can now never be reached — the ordering is enforced, not just a style preference.

try-with-resources

try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
    return reader.readLine();
}
// reader.close() has already run here, even if readLine() threw

Anything implementing AutoCloseable — a file, a database connection, a lock — declared inside the parentheses of a try is closed automatically when the block exits, success or exception, in the reverse of the order it was opened. The equivalent by hand is a finally block that has to null-check the resource (it might have failed to open) and can itself throw while closing, silently hiding the original exception — a genuine and common source of resource leaks that try-with-resources removes by construction.

Try it yourself

2 visible tests · 2 hidden tests

Implement parseAll(values). For each string in values, try Integer.parseInt. Return a Map<String, Object> with two keys: "parsed", a List<Integer> of every value that parsed successfully in order, and "failed", a List<String> of every value that raised NumberFormatException, in order. Catch that specific exception — nothing broader.

  • parseAll(["1","abc","42","3.5"])
  • parseAll(["1","2","3"])
Loading editor…

Sign up to check the hidden tests and save your progress. Sign up