Exceptions (course slides + memo)
Java.
Why exceptions
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 programCases 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
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!");
}trywraps what we're attempting;catchnames 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
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)
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
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
Exceptiontype — catch the most specific type for the situation (UnsupportedOperationException,IndexOutOfBoundsException,NullPointerException, ...). If unsure what a method can throw, check its JavaDoc. - Keep
tryblocks small — every extra statement inside is another potential jump point to acatchblock, making the flow harder to follow. Sometimes several small, separatetryblocks read better than one big one. - Prefer preventing the error over catching it, when that's possible. Don't do:javaPrefer:
public Optional<String> getFirstElement(List<String> list) { try { return Optional.of(list.get(0)); } catch (IndexOutOfBoundsException e) { return Optional.empty(); } }javapublic 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.
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.