W3docs

Java Multiple catch Blocks and Multi-catch

Catch different exception types in Java with multiple catch blocks or a single multi-catch (Type1 | Type2 e) clause.

Java Multiple catch Blocks and Multi-catch

A single try can be followed by any number of catch blocks. Each one declares a different exception type and runs only if the throw matches. This is how Java lets one block of code react differently depending on what went wrong — a network failure isn't a parse error, and you may want to handle them in completely different ways.

Multiple sequential catches

try {
  String body = httpClient.get(url);
  Config cfg  = parser.parse(body);
  apply(cfg);
} catch (IOException e) {
  retryLater(url);
} catch (ParseException e) {
  report("config file is malformed: " + e.getMessage());
} catch (SecurityException e) {
  report("not allowed to apply config");
}

Only one catch runs per throw — the first one whose type is an instance-of match for the thrown exception. After it runs, control jumps to the statement after the whole try statement, not to the next catch.

Ordering matters: specific to general

A catch (T e) matches anything that is a T or any subclass of T. If you list a superclass before its subclass, the subclass catch is unreachable, and the compiler refuses:

try { ... }
catch (Exception e)        { ... }   // matches everything below Exception
catch (IOException e)      { ... }   // ERROR: unreachable

The order to follow is narrowest type at the top, widest at the bottom:

try { ... }
catch (FileNotFoundException e) { ... }   // most specific
catch (IOException e)           { ... }   // wider
catch (Exception e)             { ... }   // catch-all (used sparingly)

This is also a useful design exercise: writing the catches forces you to think about what failures the block can actually produce.

Multi-catch (one block, several types)

Java 7 added the multi-catch form: one block handling several unrelated exception types, separated by |:

try {
  return parser.read(file);
} catch (IOException | ParseException e) {
  log.warn("could not load config: " + e);
  return Config.defaults();
}

Use this when the handling is identical for several distinct failures. It's shorter than two near-duplicate catch blocks and makes the "this and this share a response" relationship obvious at a glance.

Rules to know:

  • The types in the union must not be related by inheritance. IOException | FileNotFoundException won't compile — one is a subtype of the other, so the wider one already covers it.
  • Inside the block, e is typed as the common supertype of the listed types. You can call methods declared on that supertype, but not subtype-specific ones. For most uses (logging, wrapping), getMessage() and toString() are enough.
  • The catch parameter in a multi-catch is implicitly final — you can't reassign it. (Single-type catches are only effectively final; the difference doesn't matter in practice.)

When to split a try

A common readability mistake is wrapping an entire method in one giant try, then catching everything any line could throw. The handling logic at the bottom ends up confused about which line failed.

Two cleaner shapes when that happens:

  • Two separate try statements, each scoped to a related set of operations.
  • A try inside a method that calls smaller methods, each responsible for one kind of failure.

The smaller the try, the easier the catches are to reason about. "What line could throw this?" should always have a short answer.

A worked example

A small program that does three things — parse a number, look it up in an array, divide by it — each of which can fail in its own way. We catch each failure with a dedicated handler so the messages stay specific. The last catch uses the multi-catch form to group two failures that share a response.

java— editable, runs on the server

Each input takes a different path through the catches, but every iteration prints one clean line — the program never throws up its hands at the user.

What's next

try/catch handles the happy path and the failure path. The third clause, finally, handles the things that have to happen either way. Continue to Java finally block.

Practice

Practice

Will this compile?\n\n```java\ntry { ... }\ncatch (IOException | FileNotFoundException e) {\n log(e);\n}\n```