💥 Exceptions & Errors · Intermediate

Multi-catch in Java

catch (A | B e) and the rule about related types.

🧩 The mysteryTwo catch blocks, identical bodies, copy-pasted. Then someone fixes a bug in only one of them. Java 7 has a one-character cure.

One handler, many types

A multi-catch handles several exception types in one block, separated by **|**. Whichever of them is thrown, the same handler runs — no duplicated code.

try {
    loadUser(id);
} catch (IOException | SQLException e) {
    log.error("load failed", e);
}
🔮 Predict it

One net, two fish

What does this print?

Object[] items = {"7", 3, "z"};
for (Object o : items) {
    try {
        String s = (String) o;
        System.out.print(Integer.parseInt(s));
    } catch (ClassCastException
             | NumberFormatException e) {
        System.out.print("-");
    }
}
  1. 7--
  2. 7-
  3. 7
  4. Throws ClassCastException
Show the answer

"7" parses fine. 3 can't be cast to String (ClassCastException), and "z" can't be parsed (NumberFormatException). One handler covers both, and the loop keeps going.

What is e?

In a multi-catch, e has the type of the alternatives' closest common supertype. For FileNotFoundException | SQLException that's Exception, so you can call getMessage() without a cast. And e is implicitly final — assigning to it is a compile error.

catch (FileNotFoundException
       | SQLException e) {
    e.getMessage();   // Exception methods
    e = null;         // compile error
}
⚠️ The trap

No relatives allowed

The alternatives must not be subclasses of each other. NumberFormatException already is an IllegalArgumentException, so listing both is redundant — and a compile error.

try {
    Integer.parseInt("7");
} catch (NumberFormatException
         | IllegalArgumentException e) { // error
}

Merging handlers

✗ Too wide
try {
    process();
} catch (Exception e) {
    log(e);
}

Also swallows unrelated bugs like NullPointerException.

✓ Just right
try {
    process();
} catch (IOException | SQLException e) {
    log(e);
}

Removes the duplication without widening the net.

💼 In the real world

Taming noisy APIs

Reflection is the classic case: Class.forName(...).getMethod(...).invoke(...) can throw several checked exceptions. Multi-catch keeps that handling to one block. Java 7 also added ReflectiveOperationException as their common parent for when you want them all.

Key takeaways

  1. catch (A | B e) — one handler for several types
  2. Alternatives can't be related by subclassing
  3. e is implicitly final; you can't reassign it
  4. e's type is the closest common supertype of the alternatives
🤯 Did you know?

Multi-catch arrived in Java 7 (2011) with Project Coin — alongside try-with-resources, the diamond <> and strings in switch.

Practice questions

What does this print?

int[] d = {5, 0};
for (int i = 0; i < 3; i++) {
    try {
        System.out.print(10 / d[i]);
    } catch (ArithmeticException
             | ArrayIndexOutOfBoundsException e) {
        System.out.print("!");
    }
}
  1. 2!!
  2. 2!
  3. 2
  4. Throws ArrayIndexOutOfBoundsException
Check your answer

2!!. 10 / 5 prints 2; 10 / 0 throws ArithmeticException; d[2] throws ArrayIndexOutOfBoundsException. One handler covers both.

What does this print?

try {
    Integer.parseInt("7");
} catch (NumberFormatException | IllegalArgumentException e) {
    System.out.println("bad");
}
System.out.println("ok");
  1. ok
  2. bad ok
  3. Compile error
Check your answer

Compile error. NumberFormatException is a subclass of IllegalArgumentException, so listing both is redundant and rejected.

Next: a block that runs no matter what — even after return. But can it change what a method returns?