Java Exceptions

Handle errors with try, catch and finally, understand checked and unchecked exceptions, and throw your own.

Lesson 64 of 127Advanced18 min read

Things go wrong in every real program. Users type letters where numbers belong, files are missing, networks drop and divisions by zero sneak in. When Java meets a problem it can't continue from, it throws an exception: an object describing what went wrong. If nothing catches it, the program stops and prints a stack trace.

Handling exceptions well is what separates a program that crashes at the first surprise from one that reports a clear message, recovers where it can and cleans up after itself. Java takes this seriously: for some kinds of problem, the compiler won't even let you ignore the possibility of an error.

In this lesson you'll read stack traces, catch exceptions with try and catch, run clean-up code with finally and try-with-resources, learn the difference between checked and unchecked exceptions, and throw exceptions of your own, including custom exception classes.

Reading a stack trace

Text
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Main.average(Main.java:9)
	at Main.main(Main.java:4)
  • The first line names the exception type, ArithmeticException, and its message, / by zero.
  • The at lines show the chain of method calls, most recent first. The top one, Main.java:9, is where the problem happened; below it is who called that method.

Start at the top, open that line of code and ask what value could have caused the message. Most bugs are found in a minute this way.

try, catch and finally

Java
try {
    int n = Integer.parseInt(text);
    System.out.println(100 / n);
} catch (NumberFormatException e) {
    System.out.println("Not a number: " + text);
} catch (ArithmeticException e) {
    System.out.println("Can't divide by zero");
} finally {
    System.out.println("Done");
}
  • Code in try runs normally until an exception is thrown; Java then jumps to the first matching catch.
  • Order catch blocks from most specific to most general, because the first match wins.
  • One block can catch several types: catch (NumberFormatException | ArithmeticException e).
  • finally runs whether or not an exception happened, even after a return, which makes it the place for clean-up.

Checked and unchecked exceptions

Java has two families of exceptions:

KindExamplesRule
Unchecked (extend RuntimeException)NullPointerException, ArithmeticException, IndexOutOfBoundsException, NumberFormatExceptionUsually programming mistakes; catching is optional
Checked (extend Exception)IOException, FileNotFoundException, SQLExceptionProblems outside your control; you must catch them or declare them

To declare a checked exception instead of handling it, add throws to the method: void load(String path) throws IOException. The caller then has to deal with it. This forces every part of the program to think about failures such as missing files.

throw, custom exceptions and try-with-resources

Use throw to signal a problem yourself, usually when a method receives an invalid argument:

Java
if (amount <= 0) {
    throw new IllegalArgumentException("Amount must be positive");
}

For problems specific to your program, create your own exception class by extending Exception (checked) or RuntimeException (unchecked).

Resources such as files and network connections must be closed. try-with-resources closes them automatically, even if an exception is thrown:

Java
try (BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"))) {
    System.out.println(reader.readLine());
}

Wrapping exceptions with a cause

Low-level exceptions often make little sense to the code that called you. A NumberFormatException deep inside a settings loader means "the configuration is broken" to the rest of the program. Catch the low-level exception and throw a more meaningful one, passing the original as the cause:

Java
try {
    timeout = Integer.parseInt(settings.get("timeout"));
} catch (NumberFormatException e) {
    throw new IllegalStateException("Bad config value for timeout", e);
}

The cause is kept, so the stack trace shows both: your message first, then a Caused by: java.lang.NumberFormatException section pointing at the real source. e.getCause() returns it in code. Never throw away the original exception when wrapping; it's the most useful clue you have.

When to use exceptions

Exceptions are for unusual situations, not ordinary decisions. Checking if (text.isBlank()) is clearer and faster than letting a parse fail and catching the result. Good rules of thumb:

  • Validate inputs you can check cheaply, and throw IllegalArgumentException when a method receives a value it can't accept.
  • Catch an exception only where you can do something useful: retry, use a default, or show a helpful message.
  • Otherwise let it travel up to a place that can report it, such as main or a web framework's error handler.

The exception hierarchy and try-catch-finally lessons practise these decisions in more depth.

Java Exceptions Syntax

Exception syntax

try {
    risky();
} catch (SpecificException e) {
    System.out.println(e.getMessage());
} catch (OtherException | ThirdException e) {
    // handle either
} finally {
    // always runs
}

void method() throws IOException { }
throw new IllegalArgumentException("message");

class MyException extends Exception {
    MyException(String msg) { super(msg); }
}

Java Exceptions Summary

  • Read stack traces from the top: the type, message and line where it happened.
  • try wraps risky code; catch (Type e) handles a specific exception; finally always runs.
  • Unchecked exceptions extend RuntimeException; checked ones must be caught or declared with throws.
  • throw new IllegalArgumentException("...") reports invalid input clearly.
  • try-with-resources closes files and connections automatically.

Java Exceptions Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Handling bad input

Java
public class Main {
    static void showHalf(String text) {
        try {
            int n = Integer.parseInt(text.trim());
            System.out.println(text.trim() + " / 2 = " + (n / 2));
        } catch (NumberFormatException e) {
            System.out.println("'" + text + "' isn't a whole number");
        }
    }

    public static void main(String[] args) {
        showHalf("18");
        showHalf("twelve");
        showHalf(" 7 ");
    }
}
Output
18 / 2 = 9
'twelve' isn't a whole number
7 / 2 = 3
Line-by-line explanation
  1. Line 4parseInt throws NumberFormatException for text that isn't a number.
  2. Lines 6 to 8The program reports the problem and carries on with the next value.
  3. Line 14Integer division: 7 / 2 is 3.
Example 2

finally and the order of events

Java
public class Main {
    static int divide(int a, int b) {
        try {
            System.out.println("Dividing " + a + " by " + b);
            return a / b;
        } catch (ArithmeticException e) {
            System.out.println("Error: " + e.getMessage());
            return 0;
        } finally {
            System.out.println("Finished " + a + "/" + b);
        }
    }

    public static void main(String[] args) {
        System.out.println("Result: " + divide(10, 2));
        System.out.println("Result: " + divide(5, 0));
    }
}
Output
Dividing 10 by 2
Finished 10/2
Result: 5
Dividing 5 by 0
Error: / by zero
Finished 5/0
Result: 0
Line-by-line explanation
  1. Line 5The return value is ready, but finally still runs before the method actually returns.
  2. Lines 6 to 8Dividing an int by zero throws ArithmeticException.
  3. Lines 9 to 11finally runs in both cases.
Example 3

A custom exception

Java
class InsufficientFundsException extends Exception {
    InsufficientFundsException(double needed) {
        super(String.format("Need £%.2f more", needed));
    }
}

class Account {
    private double balance = 50;

    void withdraw(double amount) throws InsufficientFundsException {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        if (amount > balance) {
            throw new InsufficientFundsException(amount - balance);
        }
        balance -= amount;
    }
}

public class Main {
    public static void main(String[] args) {
        Account acc = new Account();
        try {
            acc.withdraw(30);
            acc.withdraw(30);
        } catch (InsufficientFundsException e) {
            System.out.println("Declined: " + e.getMessage());
        }
    }
}
Output
Declined: Need £10.00 more
Line-by-line explanation
  1. Line 1Extending Exception makes it a checked exception.
  2. Line 10throws warns callers that this method can fail this way.
  3. Line 12An unchecked exception for a programming mistake: a non-positive amount.
  4. Line 15A checked exception for a normal business situation.
  5. Lines 26 to 28The second withdrawal fails: 30 is 10 more than the remaining 20.

Common Java Exceptions Mistakes

Catching Exception and ignoring it

Don't
try {
    saveOrder(order);
} catch (Exception e) { }
Do
try {
    saveOrder(order);
} catch (IOException e) {
    System.err.println("Couldn't save order: " + e.getMessage());
}

General catch before specific

Don't
try { ... }
catch (Exception e) { ... }
catch (NumberFormatException e) { ... }   // compile error: already caught
Do
try { ... }
catch (NumberFormatException e) { ... }
catch (Exception e) { ... }

Forgetting to close a file

Don't
BufferedReader r = Files.newBufferedReader(path);
String line = r.readLine();   // if this throws, the file stays open
Do
try (BufferedReader r = Files.newBufferedReader(path)) {
    String line = r.readLine();
}

Java Exceptions in Real Projects

Exceptions in real systems

Web frameworks such as Spring catch exceptions thrown while handling a request and turn them into proper error responses, such as "404 Not Found" or "400 Bad Request". Banking code uses custom exceptions like InsufficientFundsException to make business rules explicit, and every file, database and network API in Java reports failures through checked exceptions.

Java Exceptions FAQ

What is the difference between throw and throws in Java?

throw is a statement that actually throws an exception object: throw new IllegalArgumentException("..."). throws appears in a method declaration and warns callers which checked exceptions the method may pass on: void load() throws IOException.

Does finally always run?

It runs after the try block whether it completes normally, returns or throws, and after any catch. The exceptions are extreme cases: the program calls System.exit(), the JVM crashes or is killed, or the thread never finishes the try block. Don't put a return inside finally, because it hides any exception thrown earlier.

What is the difference between an Exception and an Error?

Both extend Throwable. Exceptions describe problems a program can reasonably handle, such as bad input or a missing file. Errors, such as OutOfMemoryError and StackOverflowError, signal serious problems in the JVM or the program's resources, and ordinary code shouldn't try to catch them.

Should my custom exception be checked or unchecked?

Make it unchecked, extending RuntimeException, when it signals a programming mistake or a situation callers usually can't fix. Make it checked, extending Exception, when callers can reasonably recover and you want the compiler to remind them, such as a declined payment. Many modern libraries prefer unchecked exceptions for simplicity.

How do I print the full details of an exception?

e.printStackTrace() prints the type, message, stack trace and any causes to the error output, which is fine while learning. In real applications, pass the exception to a logger, such as logger.error("Import failed", e), so the details are stored with the rest of the program's logs.

Java Exceptions Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the code so the error is handled.

 {
    int n = Integer.parseInt("abc");
}  (NumberFormatException e) {
    System.out.println("Invalid");
}
Predict the outputeasy

What does this print?

try {
    int[] a = new int[2];
    a[5] = 1;
    System.out.println("A");
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("B");
}
Predict the outputmedium

What does this print?

try {
    System.out.println("1");
} finally {
    System.out.println("2");
}
System.out.println("3");
Multiple choicemedium

Which exception is checked?

Multiple choicehard

What does try-with-resources do?

Java Exceptions Quiz

5 questions to check what you learned.

  1. Which keyword signals an exception yourself?

    • throws
    • catch
    • raise
    • throw
  2. What's the difference between throw and throws?

    • They're the same
    • throw raises an exception; throws declares that a method may raise one
    • throws raises an exception; throw declares one
    • throw is for checked exceptions only
  3. When does a finally block run?

    • Only after an exception
    • Only without an exception
    • In both cases, even after a return
    • Only in main
  4. Which class do unchecked exceptions extend?

    • RuntimeException
    • Error
    • IOException
    • Throwable directly
  5. Why must specific catch blocks come before general ones?

    • It's faster
    • Java sorts them automatically
    • General catches only run last anyway
    • The first matching catch wins, so a general one first would make the specific one unreachable