Java Exception Handling: try/catch/finally, try-with-resources and Custom Exceptions (1Z0-831)
Java exception handling is the set of language rules for throwing, catching, cleaning up after and declaring exceptions: try/catch/finally, try-with-resources, multi-catch and your own exception classes, which Oracle 1Z0-831 objective 4.1 asks you to use correctly. This lesson covers the checked/unchecked hierarchy and the handle-or-declare rule, how to write custom exceptions with messages and causes, the exact order try, catch and finally run in, the compile-time rules for catch and multi-catch clauses, precise rethrow, how finally interacts with return values, how exceptions propagate, and how try-with-resources closes resources and suppresses secondary failures. Every code sample was compiled and run on JDK 25.
On this page10 sections
- The exception hierarchy: checked, unchecked and Error
- Custom exceptions: choosing the superclass, messages and causes
- try/catch/finally: the order statements run in
- Catch clauses: matching, ordering and what they may catch
- Multi-catch: one handler for unrelated exception types
- Rethrowing: precise rethrow from catch (Exception e)
- finally and return values: what finally can and cannot change
- Propagation: how an uncaught exception unwinds the stack
- try-with-resources: opening, closing and scope
- try-with-resources failures: suppressed exceptions and which one wins
- Classify any exception type as checked or unchecked, including custom exceptions, Error subclasses and direct Throwable subclasses
- Apply the handle-or-declare rule to methods, constructors, lambdas and the implicit close() call
- Trace the exact execution order of try, catch and finally, including nested statements, early returns and propagation across methods
- Predict when a return, jump or throw in finally overrides a pending return value or exception
- Write legal catch and multi-catch clauses and determine a multi-catch parameter's type
- Use precise rethrow and exception chaining with getMessage(), toString(), getCause() and initCause()
- Predict try-with-resources closing order, scope rules and which exception is primary or suppressed
The exception hierarchy: checked, unchecked and Error
Every exception in Java is a Throwable, and the compiler sorts them into two groups: unchecked types (RuntimeException, Error and their subclasses), which may be thrown anywhere, and checked types (every other Throwable, including Exception itself, IOException and Throwable), which must be handled or declared.
| Branch | Checked? | Typical examples | Meaning |
|---|---|---|---|
Error and subclasses | No | StackOverflowError, OutOfMemoryError, ExceptionInInitializerError | Serious JVM or linkage problems an application normally should not try to recover from |
RuntimeException and subclasses | No | NullPointerException, ArithmeticException, IllegalArgumentException (and its subclass NumberFormatException), IllegalStateException, ArrayIndexOutOfBoundsException, UncheckedIOException | Programming errors and bad arguments |
Any other Exception or Throwable | Yes | IOException, FileNotFoundException, NoSuchFileException, InterruptedException, TimeoutException, Exception, Throwable | Conditions a caller is expected to plan for |
Handle or declare. Wherever a checked exception can be thrown — a throw statement, a call to a method whose throws clause lists it, a constructor, or the hidden close() call of try-with-resources — it must be caught by an enclosing try or listed in the enclosing method's throws clause. This is a compile-time rule about what could happen: a throw new Exception(...) on a branch that never runs still fails with unreported exception Exception; must be caught or declared to be thrown.
Lambdas are judged against their functional interface. A lambda body is the implementation of the interface's single abstract method, so the only checked exceptions it may let escape are the ones that method declares. Supplier<T>.get(), Runnable.run(), Function.apply() and most java.util.function methods declare none, so Supplier<String> s = () -> readLine(); fails to compile when readLine() throws IOException. The enclosing method's throws clause and any try around the later call to get() are outside the body and do not count. Two fixes work: handle the exception inside the lambda body, or target an interface whose method declares it, such as Callable<V> (V call() throws Exception) or your own functional interface.
Error is not an Exception. Error and Exception are siblings under Throwable, so catch (Exception e) never catches an Error. Only catch (Error e), a matching Error subclass, or catch (Throwable t) does. A finally block still runs when an Error passes through. A failing static initializer is the classic case: if class Tariff { static double rate = Double.parseDouble("n/a"); } throws during class initialization, the first use of Tariff.rate receives an ExceptionInInitializerError whose getCause() is the original NumberFormatException. A catch (NumberFormatException e) around that use does not match; a catch (Error e) does.
Custom exceptions: choosing the superclass, messages and causes
A custom exception is an ordinary class that extends an exception type, and its superclass alone decides whether it is checked: extend Exception (or any checked subclass such as IOException) when callers must handle the condition, and extend RuntimeException (or a subclass like IllegalStateException) when it signals a programming error callers may ignore. Extending Error also makes it unchecked but is reserved for JVM-level problems; extending Throwable directly gives you a checked exception.
Custom exceptions normally add constructors that pass to Throwable's: super(), super(message), super(message, cause) and super(cause). Everything else is inherited:
getMessage()returns the message passed in, ornullwhen none was given.toString()— whatprintln(e)and string concatenation use — returns the class's binary name, then": "and the message only when the message is not null. A class in the unnamed package prints with its simple name; a JDK class prints fully qualified, e.g.java.lang.UnsupportedOperationException. A nested class prints with its binary name, such asOuter$Inner.toString()callsgetLocalizedMessage(), which callsgetMessage(), so overridinggetMessage()changes whattoString()and stack traces show, even if the constructor passed a different message tosuper.getCause()returnsnullunless a cause was supplied.
class OvenException extends Exception {
OvenException(String message) {
super(message);
}
}
class PantryException extends RuntimeException {
PantryException(String message) {
super(message);
}
}
public class Main {
static void bake(int minutes) throws OvenException {
if (minutes > 90) throw new OvenException("too hot");
if (minutes < 5) throw new PantryException("no flour");
System.out.println("baked " + minutes);
}
public static void main(String[] args) {
try {
bake(30);
bake(120);
} catch (OvenException e) {
System.out.println(e + " | " + e.getMessage()
+ " | " + e.getCause());
}
}
}This prints baked 30, then OvenException: too hot | too hot | null. bake must declare OvenException because it is checked, while PantryException extends RuntimeException and needs no declaration; had bake(2) been called, it would have escaped main uncaught, because nothing catches it.
Exception chaining wraps a low-level exception in one that makes sense to the caller, without losing the original:
class RecipeException extends Exception {
RecipeException(String message, Throwable cause) {
super(message, cause);
}
RecipeException(Throwable cause) {
super(cause);
}
}
public class Main {
static int servings(String text) throws RecipeException {
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
throw new RecipeException("unreadable servings", e);
}
}
public static void main(String[] args) {
try {
servings("4x");
} catch (RecipeException e) {
System.out.println(e.getMessage());
System.out.println(e.getCause());
}
var wrapped = new RecipeException(
new ArithmeticException("/ by zero"));
System.out.println(wrapped.getMessage());
try {
wrapped.initCause(new IllegalArgumentException());
} catch (IllegalStateException e) {
System.out.println("cause locked");
}
}
}Output: unreadable servings, then java.lang.NumberFormatException: For input string: "4x", then java.lang.ArithmeticException: / by zero, then cause locked. Three rules show here: super(message, cause) keeps your message and makes the original reachable through getCause(); super(cause) alone sets the message to cause.toString() (class name included), not to the cause's own message; and a cause can be set only once — calling initCause() on an exception whose cause was already set (even by a constructor) throws IllegalStateException.
try/catch/finally: the order statements run in
When code in a try block throws, the rest of the try block is skipped, the first catch whose type matches the exception runs, then finally runs, and execution continues after the whole statement. With no exception, the catch blocks are skipped and finally still runs. If the try block executes return, finally runs before the method leaves and the statements after the try statement do not run.
public class Main {
static void weigh(String kg) {
try {
System.out.print("try ");
if (kg.isEmpty()) return;
Integer.parseInt(kg);
System.out.print("parsed ");
} catch (NumberFormatException e) {
System.out.print("catch ");
} finally {
System.out.print("finally ");
}
System.out.print("after ");
}
public static void main(String[] args) {
weigh("12");
System.out.println("|");
weigh("kg");
System.out.println("|");
weigh("");
System.out.println("|");
}
}Output:
try parsed finally after |
try catch finally after |
try finally |The three calls show the normal path, a handled exception (parsed skipped, catch runs, finally runs, execution continues), and an early return (finally runs, after is skipped). A finally that only prints does not change the method's result; any statement after the try statement runs only if the try statement completed normally.
Shape rules the compiler enforces:
- A plain
tryneeds at least onecatchor afinally; a try-with-resources may stand alone. - Order is fixed:
try, any catches, then at most onefinallylast. A catch after finally does not compile. - Code that can never be reached is an error. If the try block and every catch end in
returnorthrow(and finally completes normally), a statement after the try statement is unreachable and the method fails to compile. - Try statements nest. An inner
try/finallywith no matching catch runs its finally and passes the exception outward, where the nearest enclosing matching catch handles it; any statements between the inner statement and that catch are skipped.
Catch clauses: matching, ordering and what they may catch
A catch clause matches an exception if the thrown object is an instance of the catch type, and the clauses are tried top to bottom; only the first match runs. Three compile-time rules govern which catch clauses are legal:
| Rule | Compiles? | Compiler message |
|---|---|---|
A superclass catch before a subclass catch (IllegalArgumentException then NumberFormatException) | No — the subclass catch is unreachable | exception NumberFormatException has already been caught |
A subclass catch before its superclass (EOFException then IOException, around a call that declares IOException) | Yes — the more specific handler wins when it matches | — |
Catching a checked exception the try block cannot throw (catch (CloneNotSupportedException e) around x = x + 1;) | No | exception CloneNotSupportedException is never thrown in body of corresponding try statement |
Catching Exception, Throwable or any unchecked type around anything | Yes — these could always be thrown | — |
The "never thrown" rule applies to checked types only, and a checked subclass of something the try block can throw is allowed: a call that declares IOException may really throw FileNotFoundException, so catch (FileNotFoundException e) is legal there.
What the try block can throw is judged from declarations, not from the object really thrown. Take class ShipException extends Exception, class CustomsException extends ShipException and class PermitException extends CustomsException, with static void dispatch() throws ShipException { throw new PermitException(); }. A caller must handle ShipException, the declared type: a try whose only handler is catch (CustomsException e) leaves ShipException unreported. At runtime, though, the actual class decides which catch runs: with catch (CustomsException e) followed by catch (ShipException e), the thrown PermitException lands in the first one. Listing the subclass and its superclass in one multi-catch is not an option (see the next section).
Catches guard the try block only. An exception thrown inside a catch block is not seen by the other catch clauses of the same statement; it runs the finally block and travels outward. A checked exception thrown from a catch block therefore needs an outer handler or a throws clause, even if a sibling catch (Exception e) sits right below it.
public class Main {
static void refill() {
try {
throw new UnsupportedOperationException("first");
} catch (UnsupportedOperationException e) {
System.out.print(e.getMessage() + " ");
throw new ArithmeticException("second");
} catch (ArithmeticException e) {
System.out.print("sibling ");
} finally {
System.out.print("cleanup ");
}
}
public static void main(String[] args) {
try {
refill();
} catch (RuntimeException e) {
System.out.println(e.getClass().getSimpleName());
}
}
}This prints first cleanup ArithmeticException: the ArithmeticException thrown inside the first catch bypasses the sibling catch (ArithmeticException e), finally prints cleanup, and the caller's catch (RuntimeException e) handles it.
A single-type catch parameter is an ordinary local variable, so it may be reassigned (which disables precise rethrow, below). Since Java 22 the parameter can be the unnamed variable _ when the handler does not need the exception, for example catch (ArithmeticException _) { return 0; }. _ stands only for the variable name: the exception type is still mandatory, and the unnamed variable can never be referenced, not even to read its message. It works in multi-catch too.
Rethrowing: precise rethrow from catch (Exception e)
Precise rethrow (Java 7+) means that throw e; inside catch (Exception e) is treated as throwing only the checked exceptions the try block can actually throw, provided the catch parameter is final or effectively final. That lets a method log-and-rethrow with a broad catch and still declare a narrow throws clause.
import java.io.EOFException;
public class Main {
static void readHeader(byte[] data) throws EOFException {
try {
if (data.length < 4) {
throw new EOFException("short header");
}
System.out.println("header ok");
} catch (Exception e) {
System.out.println("rejecting: " + e.getMessage());
throw e;
}
}
public static void main(String[] args) {
try {
readHeader(new byte[8]);
readHeader(new byte[2]);
} catch (EOFException e) {
System.out.println("caller got "
+ e.getClass().getSimpleName());
}
}
}Output: header ok, rejecting: short header, caller got EOFException. It compiles with only throws EOFException because the try block can throw nothing else checked; the rethrown object is the original EOFException. The rules around it:
- Every checked type the try block can throw must still be declared. If the try block could also throw
CloneNotSupportedException, the method would need both types (or supertypes that cover them); declaring only one fails. - Assigning
eswitches it off. Once the catch block assigns anything toe— even anotherEOFException— the parameter is no longer effectively final, sothrow ethrows its declared typeExceptionand the narrowthrowsclause fails withunreported exception Exception. Declaring the parameterfinalkeeps precise rethrow working (and forbids the assignment). - Adding a new checked call breaks it. If the try block gains a call that throws a wider checked type — say
Files.readAllBytes(path), which declaresIOException— the rethrow can now throwIOExceptionand athrows EOFExceptionclause no longer compiles. Widening the clause to a supertype is always fine. - With only unchecked exceptions possible,
throw efromcatch (Exception e)needs nothrowsclause at all — it simply rethrows the original object.
finally and return values: what finally can and cannot change
A return in try or catch evaluates its expression before finally runs, and that value is then held while finally executes. Finally can change variables and the objects they point to, but not the value already chosen — unless finally completes abruptly itself.
import java.util.ArrayList;
import java.util.List;
public class Main {
static int level = 3;
static int raise() {
try {
return level * 2;
} finally {
level = 100;
}
}
static List<String> collect() {
List<String> items = new ArrayList<>(List.of("x"));
try {
items.add("y");
return items;
} finally {
items.add("z");
items = List.of("ignored");
}
}
@SuppressWarnings("finally")
static String status(String code) {
try {
return "code " + Integer.parseInt(code);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("rejected");
} finally {
return "fallback";
}
}
public static void main(String[] args) {
System.out.println(raise() + " " + level);
System.out.println(collect());
System.out.println(status("A7"));
}
}This prints 6 100, [x, y, z] and fallback:
raise()returns 6 —level * 2computed whenreturnran — even though finally then setslevelto 100. A primitive return value is a copy.collect()returns the original list. Finally addedzto that same object (so the caller sees it) and then pointed the local variable at another list, which does not affect the reference already being returned.status()returnsfallback: areturnin finally replaces the pending result, and here it silently discards theIllegalArgumentExceptionthe catch block threw, so the caller never sees it.
Abrupt completion in finally always wins. A return, break, continue or throw in finally overrides whatever the try or catch was doing — a pending return value or a pending exception. A continue in finally inside a loop, for instance, swallows the iteration's exception and moves on to the next element. Similarly, an exception thrown from finally replaces the one in flight, and a plain try/finally does not record the lost exception anywhere: getSuppressed() on the new exception has length 0. Suppressed exceptions are a try-with-resources feature only. javac compiles all of these, with at most a finally clause cannot complete normally lint warning.
Propagation: how an uncaught exception unwinds the stack
An exception that no catch in the current method matches propagates to the caller, running each finally it passes through from the innermost outward, until a matching catch handles it; if none does, the thread ends and the default handler prints Exception in thread "main" … with the stack trace. Every statement between the throw point and the handler is skipped.
public class Main {
static void pack() {
try {
throw new UnsupportedOperationException("fragile");
} catch (ClassCastException e) {
System.out.print("pack-catch ");
} finally {
System.out.print("pack-finally ");
}
System.out.print("packed ");
}
static void ship() {
try {
pack();
System.out.print("shipped ");
} catch (UnsupportedOperationException e) {
System.out.print("ship-catch ");
throw new IllegalStateException("returned", e);
} finally {
System.out.print("ship-finally ");
}
}
public static void main(String[] args) {
try {
ship();
} catch (IllegalStateException e) {
System.out.println("main " + e.getMessage()
+ " <- " + e.getCause().getMessage());
}
}
}This prints pack-finally ship-catch ship-finally main returned <- fragile. In pack() the non-matching catch (ClassCastException e) is skipped, but its finally runs before the exception leaves the method, and packed never prints. Back in ship(), shipped is skipped too; the catch wraps the exception in a new one, that new exception passes through ship()'s finally, and main handles it with the original still reachable through getCause(). Unwinding skips mismatched catches but never skips a finally, and an inner finally always runs before an outer catch.
Only a catch stops an exception. A chain of try/finally blocks with no matching catch runs every finally on the way out and then terminates the thread with the original exception; code after the call in each method, and after the call in main, never runs.
try-with-resources: opening, closing and scope
Try-with-resources (try (Type r = ...) { }) automatically calls close() on every resource that implements AutoCloseable (including Closeable), whether the body completes normally, returns or throws. Resources are initialized left to right and closed right to left, right after the body and before any catch or finally of the same statement runs.
class Valve implements AutoCloseable {
private final String name;
Valve(String name) {
this.name = name;
System.out.print("open " + name + " ");
}
@Override
public void close() {
System.out.print("shut " + name + " ");
}
}
public class Main {
public static void main(String[] args) {
Valve mains = new Valve("mains");
try (mains; Valve hot = new Valve("hot");
var _ = new Valve("cold")) {
System.out.print("flow ");
throw new ArithmeticException("leak");
} catch (ArithmeticException e) {
System.out.print("patched ");
} finally {
System.out.println("off");
}
}
}This prints open mains open hot open cold flow shut cold shut hot shut mains patched off. The catch sees resources that are already closed. When try-with-resources statements are nested, the inner one closes its resources, then runs its own catch and finally, before the outer one closes and does the same.
Rules for resources:
- Resource variables are implicitly final and scoped to the try block. Assigning a declared resource inside the body fails to compile, and using the variable after the statement fails with
cannot find symbol. - Existing variables (Java 9+). The header may name an existing local variable, parameter or field that is
finalor effectively final, asmainsis above; it is closed like a declared resource. A local variable that is assigned again anywhere in the method — before or after the try statement — is not effectively final, and a non-final field is not allowed either. The header holds only declarations and variable names, never another expression such as a bare constructor call. - A null resource is allowed and simply not closed; no
NullPointerExceptionis thrown by the statement itself. - Unnamed resources (Java 22+).
var _ = new Valve("cold")declares a resource the body never refers to; it is still opened and closed. A header may declare more than one unnamed resource, but each still needs its type orvar:_replaces only the name. - Checked exceptions from close() count. The implicit
close()call is checked against the declared type of the resource variable.AutoCloseable.close()declaresthrows ExceptionandCloseable.close()declaresthrows IOException, so if your class'sclose()keeps such a clause, or the variable's declared type isAutoCloseableorCloseable, the statement fails withunreported exception …and the noteexception thrown from implicit call to close() on resource variable …. Only a catch for that checked type (or a supertype) or a matchingthrowsclause fixes it; a finally block or a catch for an unchecked type does not. - Some JDK classes narrow close(). An overriding
close()may declare fewer exceptions, and then nothing needs handling.StringReader,CharArrayReader,CharArrayWriter,ScannerandFormatterall declareclose()with nothrowsclause, sotry (var r = new StringReader("text")) { }compiles with no catch. Do not assume every in-memory class does:StringWriter,ByteArrayInputStreamandByteArrayOutputStreamstill declareclose() throws IOException.
try-with-resources failures: suppressed exceptions and which one wins
In try-with-resources the first exception thrown stays primary, and any exception thrown by a later close() is attached to it as a suppressed exception, read back with getSuppressed() in closing order. Every resource is still closed even when an earlier close fails.
class Hatch implements AutoCloseable {
private final String side;
Hatch(String side) {
if (side.isBlank()) {
throw new IllegalArgumentException("unnamed hatch");
}
this.side = side;
}
@Override
public void close() {
throw new IllegalStateException("jam " + side);
}
}
public class Main {
static void show(Exception e) {
StringBuilder line = new StringBuilder(e.getMessage());
for (Throwable s : e.getSuppressed()) {
line.append(" + ").append(s.getMessage());
}
System.out.println("[" + line + "]");
}
public static void main(String[] args) {
try (var fore = new Hatch("fore"); var aft = new Hatch("aft")) {
throw new UnsupportedOperationException("overflow");
} catch (RuntimeException e) {
show(e);
}
try (var fore = new Hatch("fore"); var aft = new Hatch("aft")) {
System.out.print("sealed ");
} catch (RuntimeException e) {
show(e);
}
try (var fore = new Hatch("fore"); var aft = new Hatch(" ")) {
System.out.print("sealed ");
} catch (RuntimeException e) {
show(e);
}
}
}Output:
[overflow + jam aft + jam fore]
sealed [jam aft + jam fore]
[unnamed hatch + jam fore]| What fails | Primary exception | Suppressed |
|---|---|---|
| Body throws, then closes throw | The body's exception | Every close failure, in closing order (reverse of declaration) |
| Body succeeds, closes throw | The first close() failure (the last-declared resource) | Later close failures |
| A resource initializer throws | The initializer's exception — the body never runs | Close failures of resources already initialized (only those are closed) |
A failing close() also defeats a return in the body: the return value is computed, close() runs, and its exception propagates instead of the value. And if a resource's constructor throws, only the resources to its left are closed; the failed one never existed, so its close() is never called. A catch or finally on the same statement runs after all of this. Contrast plain try/finally, where a second exception replaces the first and nothing is suppressed.
Tip. Objective 4.1 is examined through short, complete programs that ask for the exact output, whether the code compiles, or which exception escapes at runtime. Flow questions place print statements in try, catch, finally and in nested or calling methods to test execution order, early returns, propagation and what finally does to a pending return or exception. Compile questions offer several catch, multi-catch, throws-clause or resource-header variants and ask which ones are legal, so the rules on catch ordering, unthrown checked exceptions, subclass-related alternatives, final parameters, precise rethrow and effectively final resources matter. Try-with-resources questions combine failing bodies, constructors and close() methods and ask which exception is primary, what getSuppressed() holds and in what order resources close. Custom-exception questions turn on the chosen superclass and on what getMessage(), toString() and getCause() return.
- Unchecked means RuntimeException or Error (and subclasses); every other Throwable, including Exception and Throwable itself, is checked and must be caught or declared.
- catch (Exception e) never catches an Error; finally still runs for one, and a failing static initializer surfaces as ExceptionInInitializerError with the original as its cause.
- Catches are tried top to bottom: a superclass before its subclass fails to compile, and catching a checked exception the try block cannot throw fails too.
- An exception thrown inside a catch block goes outward through finally, never to a sibling catch.
- Multi-catch alternatives must be unrelated, the parameter is implicitly final, and its type is the alternatives' common supertype.
- A return value is fixed before finally runs; a return, continue, break or throw in finally overrides it or any pending exception, and plain try/finally suppresses nothing.
- Try-with-resources closes right to left, before its own catch and finally run; resource variables are final and scoped to the try block, and null resources are skipped.
- The first exception thrown in try-with-resources is primary; later close() failures are suppressed onto it, and a failed initializer closes only the resources before it.
- A custom exception's toString() is ClassName: message, getMessage() is null when no message was passed, and super(cause) alone copies cause.toString() into the message.
Frequently asked questions
What is the difference between checked and unchecked exceptions in Java?
Unchecked exceptions are RuntimeException, Error and their subclasses; the compiler lets you throw them anywhere without handling. Every other Throwable, including Exception, IOException and Throwable itself, is checked: wherever one can be thrown it must be caught by an enclosing try or listed in the method's throws clause, or the code does not compile.
Does finally run if the try block returns?
Yes. When try or catch executes return, the return expression is evaluated first, then finally runs, then the method returns that saved value. Finally can change variables or mutate a returned object, but it cannot change the saved value unless finally itself executes return, throw, break or continue, which overrides the pending result.
What happens if both the try block and finally throw an exception?
In a plain try/finally, the exception thrown by finally replaces the original, which is lost: it is not recorded as suppressed. Only try-with-resources keeps both, making the body's exception primary and attaching close() failures to it through getSuppressed().
In what order does try-with-resources close resources?
Resources are initialized in the order they are declared and closed in the reverse order, immediately after the try block ends and before any catch or finally of the same statement runs. If a resource's initializer throws, the body is skipped and only the resources already initialized are closed.
Why can't a multi-catch list a subclass and its superclass?
Java forbids alternatives in a multi-catch that are related by subclassing, because catching the superclass already covers the subclass. A clause such as catch (EOFException | IOException e) is rejected with the message that alternatives in a multi-catch statement cannot be related by subclassing; catch the superclass alone, or use separate catch clauses with the subclass first.
Can you reassign the exception variable in a catch block?
In a single-type catch, yes: the parameter is an ordinary local variable, although assigning it disables precise rethrow. In a multi-catch, no: the parameter is implicitly final and any assignment is a compile error. Try-with-resources variables are implicitly final as well.
How do I create a custom checked exception in Java?
Declare a class that extends Exception (or another checked type such as IOException) and add constructors that call super(message), super(message, cause) or super(cause). Extending RuntimeException instead makes it unchecked. The class inherits getMessage(), toString() and getCause() from Throwable.
What is precise rethrow in Java?
If a catch (Exception e) block rethrows e with throw e and never assigns e, the compiler treats the rethrow as throwing only the checked exceptions the try block can actually throw. The method can then declare those narrow types instead of Exception. Reassigning e, or adding a call that throws a new checked type, removes that benefit.
Source
This lesson covers the "Implementing Exception Handling in Java Applications" domain of the official 1Z0-831 exam guide. Vendors revise their guides — check the source for the current version.
- Java SE 25 Developer Professional (1Z0-831) exam page and topics — Oracle University
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.