Skip to content

Exceptions (course slides + memo)

Java.

Why exceptions

java
List<String> names = List.of("Lisa", "Sarah");
String thirdElement = names.get(2);
System.out.println(thirdElement);
// no third element -> throws an exception; unhandled, this crashes the program

Cases like these are where the language needs help from us to say what should happen instead: calling .get() on an empty/too-short list (IndexOutOfBoundsException), calling a method on something that doesn't exist (NullPointerException), reading a file that doesn't exist (NoSuchFileException).

Handling exceptions

java
List<String> names = List.of("Lisa", "Sarah");
try {
    String thirdElement = names.get(2);
    System.out.println(thirdElement);
} catch (Exception e) {
    System.out.println("No third element in the list!");
}
  • try wraps what we're attempting; catch names the exception type we expect and what to do instead if it happens.
  • Different operations can throw different exception types, and we handle each accordingly.

Scope rules

java
try {
    String thirdElement = names.get(2);
} catch (Exception e) {
    System.out.println("No third element in the list!");
}
System.out.println(thirdElement); // won't compile - out of scope here!

A variable declared inside a try block doesn't exist outside it.

Another example (input loop)

java
while (true) {
    Scanner scanner = new Scanner(System.in);
    System.out.println("Enter a number:");
    try {
        int number = scanner.nextInt();
        System.out.println("Your number is: " + number);
    } catch (Exception e) {
        System.out.println("You did not enter a valid number!");
    }
}

Exception types

java
List<String> names = getNames();
try {
    String firstNameInList = names.get(0);   // can throw IndexOutOfBoundsException
    System.out.println(firstNameInList);
    names.add("Joe");                        // can throw UnsupportedOperationException (if immutable)
} catch (IndexOutOfBoundsException e) {
    System.out.println("There are no names in the list.");
} catch (UnsupportedOperationException e) {
    System.out.println("The list can't be modified.");
}

Best practices

  • Don't catch the generic Exception type — catch the most specific type for the situation (UnsupportedOperationException, IndexOutOfBoundsException, NullPointerException, ...). If unsure what a method can throw, check its JavaDoc.
  • Keep try blocks small — every extra statement inside is another potential jump point to a catch block, making the flow harder to follow. Sometimes several small, separate try blocks read better than one big one.
  • Prefer preventing the error over catching it, when that's possible. Don't do:
    java
    public Optional<String> getFirstElement(List<String> list) {
        try {
            return Optional.of(list.get(0));
        } catch (IndexOutOfBoundsException e) {
            return Optional.empty();
        }
    }
    Prefer:
    java
    public Optional<String> getFirstElement(List<String> list) {
        if (list.isEmpty()) {
            return Optional.empty();
        }
        return Optional.of(list.get(0));
    }

Propagating exceptions

Principle: "throw early, catch late" — often we don't want to try/catch right where the risky call happens. Instead we propagate the exception (throws in the method signature), letting the caller decide whether to handle it or propagate it further. This is described as the exception "dropping down the caller stack" until some method finally catches it.

java
public List<String> read(String file) throws IOException {
    Path path = Path.of(file);
    return Files.readAllLines(path);
}

public static void main(String[] args) {
    try {
        read("src/io/everyonecodes/doc.txt");
    } catch (IOException e) {
        e.printStackTrace();
        // TODO: Handle exception...
    }
}

Deciding where in the call chain to actually handle an exception isn't trivial — it depends on the specific application.

What's coming later

  • Not needed for most things covered so far, but needed for file I/O.
  • Some exceptions the compiler forces you to handle (checked exceptions) — seen with files.
  • You can define your own exception types, throw exceptions yourself, and choose to handle them or delegate to the caller (propagation).
  • Good error handling in general is genuinely difficult, not a solved problem.