Multiple Catch

Handle several distinct failure types from one try block, either with several ordered catch clauses or a single Java 7+ multi-catch clause.

A single try block can fail in more than one way. Java lets you attach multiple catch blocks to one try, each handling a different exception type, or combine related types into one multi-catch clause so you don't repeat identical handling logic.

What Is Multiple Catch

When code inside a try block can throw different kinds of exceptions that need different responses, you attach more than one catch block after the try. At runtime the JVM walks the catch blocks in source order and runs the first one whose exception type matches (or is a supertype of) the thrown exception's class.

Only one catch block ever executes per exception thrown; execution then resumes after the whole try-catch construct (unless that catch block itself throws or returns).

Basic Syntax

Each catch block declares exactly one exception type here (a 'catch parameter') and a block of handling code. Only the branch matching the actual thrown type runs.

import java.io.IOException;

public class MultiCatchDemo {
    public static void main(String[] args) {
        try {
            processFile(args.length > 0 ? args[0] : null);
        } catch (NullPointerException e) {
            System.out.println("No file name was supplied: " + e.getMessage());
        } catch (NumberFormatException e) {
            System.out.println("File contained a bad number: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("I/O problem: " + e.getMessage());
        }
    }

    static void processFile(String name) throws IOException {
        if (name == null) {
            throw new NullPointerException("file name is null");
        }
        int value = Integer.parseInt(name); // may throw NumberFormatException
        if (value < 0) {
            throw new IOException("negative id not allowed");
        }
    }
}

Catch Block Ordering Rules

The compiler enforces that a more specific exception type must be caught before a more general one when they are related by inheritance. If a broader supertype (like Exception) is listed first, it would 'shadow' every subtype listed after it, so the compiler treats that later catch block as unreachable code and refuses to compile.

Ordering only matters between types that are actually related by inheritance. Unrelated sibling types (e.g. IOException and SQLException) can be listed in any order relative to each other.

try {
    riskyOperation();
} catch (IllegalArgumentException e) {
    // more specific — must come first
    System.out.println("Bad argument: " + e.getMessage());
} catch (RuntimeException e) {
    // more general — comes after
    System.out.println("Other runtime problem: " + e.getMessage());
}

Multi-Catch: Combining Related Types (Java 7+)

When two or more unrelated exception types need the exact same handling code, repeating that code in separate catch blocks duplicates logic. Since Java 7, the multi-catch syntax lets you list several types in one catch clause separated by the pipe character |.

This is not just syntactic sugar with identical bytecode cost avoided elsewhere — it also produces a single compiled catch block instead of duplicated bytecode, and it documents intent: these failures really are handled the same way.

try {
    parseAndConnect(input);
} catch (NumberFormatException | IllegalStateException e) {
    // one handler for two unrelated exception types
    System.out.println("Rejected input: " + e.getClass().getSimpleName()
                        + " - " + e.getMessage());
}

Multi-Catch and Related Exception Types

The compiler forbids listing two types in the same multi-catch clause when one is a subtype of the other (e.g. IOException | FileNotFoundException), because that combination is always redundant — catching the supertype alone already covers the subtype. This is a compile-time error, not a runtime one.

Inside a multi-catch block, the exception variable's static type is the least upper bound of the listed types (often Throwable-family common ancestor), and it is implicitly final — you cannot reassign it within the block.

// Does NOT compile:
// catch (IOException | FileNotFoundException e) { ... }
// error: FileNotFoundException is a subclass of IOException
// Alternatives: keep separate catch blocks, or drop the redundant subtype.

Unreachable Catch Blocks

A catch block becomes unreachable, and therefore a compile error, whenever an earlier catch block in the same try statement already matches every exception the later one could match. This most commonly happens by putting a broad type like Exception or Throwable first.

The fix is to reorder so specific types come first, or to remove the now-redundant narrow catch entirely if the broad handling is genuinely sufficient.

try {
    doWork();
} catch (Exception e) {
    System.out.println("generic handler");
} catch (ArithmeticException e) {
    // COMPILE ERROR: ArithmeticException is already caught above
    System.out.println("math error");
}

Practical Usage

Multiple/multi-catch is most useful at integration boundaries — parsing input, calling external APIs, doing I/O — where several distinct, independently likely failure modes need distinguishable (or deliberately identical) responses, such as returning different HTTP status codes or user-facing messages.

import java.nio.file.*;
import java.io.IOException;

String loadConfig(String path) {
    try {
        return Files.readString(Path.of(path));
    } catch (NoSuchFileException e) {
        return "# default config (file missing)";
    } catch (java.nio.file.InvalidPathException | IOException e) {
        System.err.println("Could not read config: " + e.getMessage());
        return "# default config (read error)";
    }
}

Edge Cases

A try block with multiple catch clauses can still have a finally block; finally always runs after whichever single catch block (if any) executed. Rethrowing from within one catch block is legal and propagates a new or the same exception upward, skipping any sibling catch blocks (they only apply to the original try, not to code inside another catch).

Catching Throwable alongside catching Error-family subtypes should be done rarely and deliberately — swallowing Errors like OutOfMemoryError usually hides serious, often unrecoverable problems rather than fixing them.

Mental Model

Think of a stack of labeled nets under a tightrope walker (the try block). Each catch clause is one net labeled with the type(s) it accepts. When the walker falls (an exception is thrown), they drop straight down and land in the first net, top to bottom, whose label matches. A multi-catch clause is one wide net with two labels stitched onto it.

Quick Reference

try { ... }
catch (SpecificEx e) { ... }              // most specific first
catch (OtherEx1 | OtherEx2 e) { ... }     // multi-catch: unrelated types, one handler
catch (GeneralEx e) { ... }               // most general last
finally { ... }                            // optional, always runs

Rules:
- Subtype-before-supertype ordering is compiler-enforced.
- Multi-catch types must not be subtype-related to each other.
- Multi-catch variable is implicitly final.

Key Takeaways

Multiple catch blocks let one try handle several exception types differently, matched top to bottom against the actual thrown type.

More specific types must be listed before their supertypes, or the compiler rejects the now-unreachable broader-first block.

Multi-catch (type1 | type2) merges identical handling for unrelated exception types into one clause and one compiled block.

Types combined in a multi-catch clause must not be related by inheritance; the caught variable is implicitly final.

Only one catch block runs per thrown exception; finally still runs regardless of which branch fired.

try { risky(); } catch (Exception e) { log(e); } catch (ArithmeticException e) { // never reached handleMath(e); } try { risky(); } catch (ArithmeticException e) { handleMath(e); } catch (Exception e) { log(e); }

Order matters: list subtypes before supertypes. Exception first swallows every exception, making the later ArithmeticException catch unreachable and a compile error.

catch (IOException | FileNotFoundException e) { // FileNotFoundException is-a IOException } catch (FileNotFoundException e) { handleMissing(e); } catch (IOException e) { handleOtherIo(e); }

You cannot combine a type and its own subtype in a multi-catch clause — it's flagged as a compile error because it's always redundant.

catch (NumberFormatException | IllegalStateException e) { e = new RuntimeException("wrapped"); // reassigning } catch (NumberFormatException | IllegalStateException e) { throw new RuntimeException("wrapped", e); }

A multi-catch parameter is implicitly final; you cannot reassign it. Wrap it into a new exception and throw that instead.

Why must a more specific exception type be caught before a more general one in the same try statement?

Because catch blocks are matched top to bottom by the first assignable type; if the general type came first it would always match, making the specific block's code unreachable, which the compiler rejects at compile time.

What compile-time restriction applies to the types listed in a multi-catch clause?

None of the listed types may be a subtype of another listed type in the same clause, since that combination would always be redundant.

Can you reassign the exception variable inside a multi-catch block? Why or why not?

No. The variable in a multi-catch clause is implicitly final because its static type is a synthesized common type, so the compiler disallows reassignment.

What is the main benefit of multi-catch over writing two separate catch blocks with identical bodies?

It avoids duplicating the same handling code and produces one compiled catch block instead of two, while documenting that the two failure types are intentionally treated alike.

If two catch blocks both could theoretically handle a thrown exception, how many of them actually execute?

Only one — the first one in source order whose type matches the exception's runtime class (or a supertype of it).

Interview: Why does Java enforce catch-block ordering at compile time instead of just picking the first match at runtime and letting later ones be dead code silently?

Because a later, provably unreachable catch block almost always signals a programming mistake (wrong order or an unnecessary block); Java treats it as a compile error rather than silently accepting dead code, consistent with its general 'no unreachable statements' rule.

Write a method readNumber(String s) that parses s to an int, catching NumberFormatException and NullPointerException with a single multi-catch clause that returns -1 and prints the exception's simple class name.

static int readNumber(String s) { try { return Integer.parseInt(s); } catch (NumberFormatException | NullPointerException e) { System.out.println("Failed: " + e.getClass().getSimpleName()); return -1; } }

Integer.parseInt(null) throws NullPointerException and Integer.parseInt("abc") throws NumberFormatException; both are unrelated types so a multi-catch clause can legally combine them.

What happens when this code is compiled?

It fails to compile: IllegalStateException is a subtype of RuntimeException, and since RuntimeException is caught first, the second catch block is unreachable.

Write a program that attempts to open a file path and either divide by a parsed integer from its first line. Use separate catch blocks (not multi-catch) for IOException and ArithmeticException, correctly ordered with any needed general catch last.

import java.nio.file.*; import java.io.IOException; public class Divider { public static void main(String[] args) { try { String line = Files.readAllLines(Path.of("value.txt")).get(0); int divisor = Integer.parseInt(line); System.out.println(100 / divisor); } catch (IOException e) { System.out.println("Could not read file: " + e.getMessage()); } catch (ArithmeticException e) { System.out.println("Divide by zero: " + e.getMessage()); } catch (RuntimeException e) { System.out.println("Unexpected: " + e.getMessage()); } } }

IOException and ArithmeticException/RuntimeException are unrelated branches of Throwable, so they can appear in either order relative to each other, but RuntimeException must come after ArithmeticException since it is its supertype.

try {
    throw new IllegalStateException("bad state");
} catch (RuntimeException e) {
    System.out.println("A");
} catch (IllegalStateException e) {
    System.out.println("B");
}