AlgoMaster Logo

Multiple Catch Blocks

High Priority20 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

A single try block often runs code that can fail in more than one way. Parsing a price string can throw NumberFormatException. Reading a cart slot can throw ArrayIndexOutOfBoundsException. Accessing a method on a null product can throw NullPointerException. Each of these failures usually deserves its own response, not a single generic "something went wrong" message. This lesson covers how to attach multiple catch blocks to one try, the rules for ordering them, the multi-catch syntax that combines several types into a single handler, and the control-flow guarantees that hold once a match is found.

Why You'd Want More Than One Catch

The previous lesson showed the basic try / catch shape: one try block, one catch block, one handler for one exception type. That shape is enough when only one kind of failure is possible, or when every failure should be handled the same way. Both conditions break down quickly in real code.

Consider a small piece of order-processing code. It reads a price string from somewhere outside the program, parses it into a number, and uses it as part of a calculation. Three different things can go wrong on three different lines.

The code runs, the exception is caught, and the program doesn't crash. The message that reaches the user is generic, though. The code can't tell whether the cart slot was missing, the price string was malformed, or the divisor was zero. All three failures route into the same handler and produce the same vague output. The caller of this method has no way to recover differently, because the only information it gets back is "something failed."

A better approach is to handle each exception type separately. The response can then match the cause: prompt for a valid slot when the index is out of bounds, ask for a numeric price when parsing fails, fall back to a sensible default when a divide-by-zero happens. Multiple catch blocks make this possible.

Each catch block names a different exception type. When the try block throws, Java walks the list of catch blocks in source order and picks the first one whose type matches the thrown exception. The matching block runs, and the others are skipped. Control then continues after the entire try / catch structure.

Three different inputs would produce three different outputs from the same code, each one specific to what went wrong. That's the payoff for splitting the handler.

The Syntax

The general shape is one try block followed by two or more catch blocks, each catching a different exception type:

A few rules apply.

Each catch block declares one exception type and one parameter name. The parameter is scoped to that catch block only, which means each block can reuse the same parameter name e without clashing. There's no conflict because e in the first block goes out of scope before e in the second block comes into scope.

The exception types must be different across the blocks. Two catch blocks with the exact same type cause a compile error, because the second one would be unreachable.

You can have as many catch blocks as you need. Two, three, five, ten. The compiler doesn't impose a numeric limit. In practice, more than four or five is a sign that the method is doing too much and could be split.

The structure applied to a slightly larger example that touches all four E-Commerce failures listed at the start of the lesson:

The try block runs three operations successfully (the array access, the parse, and the implicit arithmetic), then trips on customerName.length(), which throws NullPointerException because customerName is null. The third catch block matches that type and prints its message. The fourth catch block never gets a chance to run, because at most one catch per try executes.

The Order Rule: Most Specific First

The rule for ordering catch blocks is simple to state and easy to get wrong: when two exception types in your catch list are related by inheritance, the more specific one must come first.

Java's exception types form a class hierarchy. Throwable sits at the top. Exception extends Throwable. RuntimeException extends Exception. Specific exceptions like NumberFormatException, ArrayIndexOutOfBoundsException, and NullPointerException extend RuntimeException. A diagram makes the chain easier to see.

The four leaf types at the bottom are the ones we've been catching. They're all subtypes of RuntimeException, which is a subtype of Exception, which is a subtype of Throwable. When you write catch (Exception e), you're catching anything that is an Exception or a subtype of it, which includes every leaf in the diagram.

That inclusiveness is what makes ordering matter. If catch (Exception e) appears before catch (NumberFormatException e) in the same try, the general handler matches every exception first, and the specific handler never gets a chance to run. The compiler refuses code where this is true, because an unreachable catch block is almost certainly a bug.

The broken version. It looks fine at a glance.

What's wrong with this code?

The Exception catch comes first. NumberFormatException is a subtype of Exception, so the first catch already covers every NumberFormatException that could be thrown. The second catch can never match, because by the time the runtime gets there, the first one has already handled the exception. The compiler spots this and refuses to build the code with an error similar to:

Java's compile-time check for this case prevents a class of silent runtime bugs. Languages without this check let you write the same broken catch order, the general handler swallows everything, and the specific handler sits there unused.

Fix: put the most specific type first.

Now the specific handler is reachable. NumberFormatException matches first and runs. The Exception catch acts as a fallback for any other kind of exception the try block might produce.

The rule generalizes: when two catch types are related by inheritance, the subtype goes first. When two catch types are unrelated (NumberFormatException and ArrayIndexOutOfBoundsException, for example), their order doesn't matter, because neither covers the other. The compiler is happy either way.

A practical guideline that follows from this rule: catch specific types you know how to handle, and catch broad types only as a last resort. If your only handler is catch (Exception e), you're acknowledging that you can't tell different failures apart, which usually means you're treating success and "something arbitrary went wrong" as the only two outcomes. That's rarely what you want.

Multi-Catch: Combining Types in One Block

Sometimes two different exception types deserve identical handling. The cart-slot error and the parse error might both result in the same user-facing message and the same retry prompt. Writing two catch blocks with the same body works, but it duplicates code, and any later change has to be made in both places.

Java 7 introduced multi-catch to remove that duplication. The syntax lists multiple types separated by | (pipe) inside a single catch clause:

A concrete example. Suppose the program treats "user provided bad input" identically whether the input came in as a wrong slot number or a malformed price string. Both cases should print one message and continue.

The single catch block handles either kind of failure. The parameter e inside the block has type RuntimeException (the nearest common supertype of the two listed types), so it offers only the methods that both exception types share. In practice that's the methods from Throwable: getMessage, getClass, printStackTrace, and so on, which is usually all you need in a combined handler.

The rules for multi-catch are tight.

First, the listed types must be unrelated by inheritance. Writing catch (RuntimeException | NumberFormatException e) is a compile error, because NumberFormatException is already a subtype of RuntimeException and the broader type already covers it. The compiler message is error: Alternatives in a multi-catch statement cannot be related by subclassing.

Second, the parameter is effectively final. You cannot reassign e inside a multi-catch block. A single-type catch allows reassignment (though almost no one does it); a multi-catch forbids it. The rationale is that the parameter's type is the common supertype, but the runtime instance could be any of the listed concrete types, and the compiler tracks the actual type narrowly for things like rethrow analysis. Allowing reassignment would undermine that tracking. The restriction rarely matters in everyday code.

Third, multi-catch obeys the same ordering rule as separate catches. The types inside a single multi-catch are siblings or unrelated; the multi-catch block as a whole still needs to come before any broader catch like catch (Exception e).

When to use multi-catch: when the handling is identical, and there's no useful type-specific method to call. If you'd want to call getNumberOfMissingFields() on one but not the other, the types belong in separate catches. If both branches would print a message and continue, multi-catch is the shorter form.

A counter-example. Suppose handling NumberFormatException for a price field needs to log the offending string for debugging, while handling ArrayIndexOutOfBoundsException needs to log the bad index. The two cases now have type-specific data to extract, so a combined handler stops paying off:

Each handler reaches for context the other one wouldn't have. Combining them would force one shared message that fits neither case well.

Control Flow: At Most One Catch Runs

A try followed by several catch blocks looks like a list of options, but the runtime treats it as a chain. When the try block throws, Java picks at most one matching catch block and runs it. After that block finishes, control falls through to whatever follows the try / catch structure. The other catch blocks are skipped completely.

The flow looks like this:

The diagram shows three useful facts.

First, the try block either completes without throwing (left path) or throws (right path). In the no-throw case, every catch block is skipped.

Second, when an exception is thrown, the runtime walks the catch list in source order and picks the first match. Only that one block runs.

Third, after the matching catch runs (or after the try completes normally), control resumes at the statement immediately after the entire try / catch structure. Any code after a catch block but inside it does not affect any sibling catch. The blocks are alternatives, not sequential steps.

A short program that makes the "only one runs" guarantee visible:

The parse throws NumberFormatException. The first catch matches, runs, and prints its line. The second and third catches don't run, even though the third is broad enough to have matched. The "After the try-catch" line confirms that control resumed after the structure once the matching catch finished. The whole sequence is two lines of output, never four.

One more nuance. A catch block is regular code, which means it can itself throw an exception, return early from the method, or even start a new try / catch. None of that changes the rule that at most one sibling catch runs. If a catch throws, the new exception propagates up the call stack; sibling catches in the same try are not consulted again. Catches are alternatives to each other, not a chain of fallbacks within a single exception.

Throwing and catching an exception involves building a stack trace, which is more expensive than a normal method call. Use exceptions for exceptional conditions, not as a substitute for if / else on routine input.

A Worked Example: Parsing a Cart Average Rating

Pulling the pieces together with a complete E-Commerce scenario. The program reads a list of review-rating strings, parses them into numbers, and prints the average. Each step can fail in a different way, and each failure deserves a different message.

The fourth element of ratings is the string "three", which Integer.parseInt can't convert. The exception's message includes the offending input, which the first catch echoes back so the user knows which value caused the problem.

Change the input array to all numeric values but make the array empty, and a different branch takes over:

The loop runs zero times. count is 0. The division sum / count triggers ArithmeticException because integer divide-by-zero is an error in Java. The third catch matches and prints its message.

A single piece of code now responds intelligently to three categorically different failure modes, with no nested if checks against magic values, no flags, and no manual sentinel returns. The exception type itself is the signal; the catch block matched to that type is the response.

If you wanted to combine the "user supplied bad data" cases into one branch and keep the divide-by-zero separate, multi-catch would let you write:

Both shapes are valid. Which one to pick depends on whether the responses should differ or not. The compiler accepts both; the choice is purely about code clarity.

When to Keep Catches Separate vs Combine Them

A practical checklist for choosing between several single-type catches and a multi-catch:

SituationUse
Different responses per typeSeparate catches
Type-specific method to call on the exceptionSeparate catches
Identical message and identical recoveryMulti-catch
Logging the type name only, no other type-specific dataMulti-catch
Types related by inheritanceSeparate catches (multi-catch forbids it)

The table reads top to bottom as a tiebreaker. If the first row applies, keep them separate. If the responses are truly identical, multi-catch is shorter and easier to maintain. The middle rows handle the in-between cases where the answer depends on what the handler actually needs to do with the exception object.

A useful check: if you find yourself writing the same string literal in two adjacent catch bodies, multi-catch is probably appropriate. If the bodies differ even slightly in the data they pull from the exception or the context, leave them as separate catches and accept the small duplication of the catch keyword and braces.

A Common Mistake: Catch Order That Looks Reasonable

When the exception hierarchy isn't fresh in your mind, it's easy to write catch orders that look reasonable but compile incorrectly or silently shadow narrower handlers.

What's wrong with this code?

The broad RuntimeException catch is first. NullPointerException and ArrayIndexOutOfBoundsException are both subtypes of RuntimeException, so the first catch already covers them. The compiler refuses to build the code, with errors for the second and third catches:

Fix: put the specific catches first and use the broad catch as a final fallback (or drop the broad catch entirely if you don't need it).

Now the targeted handlers run first and the broad fallback only kicks in for runtime exceptions that aren't among the listed specific types. The dereference item.length() on a null value triggers NullPointerException, which the first catch handles. The output is the specific message, not the generic one.

Quiz

Multiple Catch Blocks Quiz

10 quizzes