Skip to content
Ameen Abozaid
PublishedBEG-029-L03

try, catch, finally

  • Lesson 3 of 8
  • 12 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

You know what the alarm is and which alarms the compiler insists on. Now we write the emergency plan. In Java the plan has three parts: a try block holding the risky code, one or more catch blocks saying what to do for each kind of problem, and an optional finally block for the things that must happen whatever occurs.

With the plan in place, one bad input no longer ends the program. The failing line is skipped, your catch block runs, and execution continues after the whole statement. Compare this with lesson 1, where the same failure ended the program.

Why this matters: nearly every real program must keep working when something goes wrong in one part: one bad row in a file, one mistyped amount, one customer asking for too much. This statement is how you contain a failure.

This lesson builds on the hierarchy from lesson 2 (you will catch by type and use the tree to order catches), on Scanner and Integer.parseInt from BEG-011, and on methods and loops.

Learning Objectives

By the end of this lesson you will be able to:

  • Write a try/catch statement and trace which lines run and which are skipped.
  • Add a finally block and explain when it runs.
  • Put several catch blocks in a correct order, from specific to general.
  • Combine alternatives with a multi-catch where the handling is identical.
  • Choose the narrowest useful exception type for a catch.
  • Write a retry loop that re-asks for input when parsing fails.

Written Explanation

The basic shape

java
try {
    // code that might throw
} catch (SomeException e) {
    // plan for SomeException; e is the exception object
} finally {
    // always runs
}

The flow is:

  1. Java runs the try block line by line.
  2. If nothing is thrown, all catch blocks are skipped.
  3. If an exception is thrown, the rest of the try block is skipped, and Java looks for the first catch whose type matches the exception (the same type, or a parent of it). That block runs, and the exception object is available through the parameter, here e.
  4. After the catch block, execution continues with the code after the whole statement. The program is not stopped.
  5. If no catch matches, the exception keeps propagating up the call stack as in lesson 1.

The variable e has methods you will use: e.getMessage() returns the text, and the exception can be printed or passed on. The next lessons add more.

Catch the narrowest useful type

The type in catch should be the most specific one you can really deal with. catch (NumberFormatException e) says "I know what to do when text is not a number". catch (Exception e) says "anything at all", which includes bugs you did not expect, such as a NullPointerException from a different line, and it makes them all look like bad input. A catch block is a plan; you cannot write a good plan for "anything".

Do not catch more than you handle, and never leave the block empty. An empty catch swallows the problem silently, and lesson 8 of this module is entirely about finding one. At the least, a catch should tell the user or the log what went wrong.

Several catch blocks

If a try can fail in different ways, add one catch per kind. Java checks them top to bottom and runs the first match, so put the specific types first and the general ones last. If a general type comes first, the specific one below can never be reached, and the compiler refuses:

java
try {
    Integer.parseInt("x");
} catch (RuntimeException e) {
    System.out.println("runtime");
} catch (NumberFormatException e) {
    System.out.println("number");
}
text
Order.java:7: error: exception NumberFormatException has already been caught

NumberFormatException is a subclass of RuntimeException, so the first block already catches it. Swap the blocks and it compiles.

Multi-catch

Sometimes two different exceptions need exactly the same response. Instead of repeating the block, list the types with |:

java
catch (NumberFormatException | ArithmeticException e) {
    System.out.println("Cannot compute: " + e.getMessage());
}

The Oracle tutorial describes this form, and notes the catch parameter is implicitly final, so you cannot assign to e in the block. The types must be unrelated: if one alternative is a subclass of another, the compiler rejects it:

text
Multi.java:5: error: Alternatives in a multi-catch statement cannot be related by subclassing

Use multi-catch only when the response really is the same. If the user-friendly message differs, use separate blocks.

finally

A finally block is for code that must run whether or not the try succeeded. The Oracle tutorial says it runs whenever the try block exits, including normal completion, an exception, and an early return, break, or continue. It is the place for cleanup, such as closing something or recording that a step ended.

A short demonstration with return:

java
static int test() {
    try {
        System.out.println("in try");
        return 1;
    } finally {
        System.out.println("in finally");
    }
}

Real output of System.out.println("result " + test());:

text
in try
in finally
result 1

The finally ran after return 1 was decided and before the method actually returned.

The same tutorial points out that finally may not run if the JVM exits while the try or catch code is running. Calling System.exit(...) is the usual way that happens, though the tutorial page does not name it, so treat that example as general Java knowledge. Do not rely on finally to run after a forced shutdown.

For closing files and connections, lesson 6 introduces a better tool than finally. For now, finally is the general mechanism, and you should be able to read code that uses it.

Putting it together

A try needs at least one catch or a finally. You can have any number of catch blocks and at most one finally, always last.

Examples

Example 1: parsing with a plan (simple)

java
public class Parse {
    static void read(String text) {
        try {
            int n = Integer.parseInt(text);
            System.out.println("Parsed " + n);
        } catch (NumberFormatException e) {
            System.out.println("'" + text + "' is not a whole number.");
        } finally {
            System.out.println("Finished reading '" + text + "'");
        }
    }

    public static void main(String[] args) {
        read("42");
        read("4x2");
        System.out.println("Program continues");
    }
}

Real output:

text
Parsed 42
Finished reading '42'
'4x2' is not a whole number.
Finished reading '4x2'
Program continues

With "42" the catch is skipped and the finally runs. With "4x2" the line Parsed ... is skipped, the catch runs, and the finally runs after it. In both cases the program continues: the last line is printed. The message quotes the offending text, so the user sees what was wrong.

Example 2: two kinds of failure in one try (practical)

java
public class Divide {
    static void divide(String[] values, int index, int divisor) {
        try {
            int number = Integer.parseInt(values[index]);
            System.out.println(number / divisor);
        } catch (NumberFormatException | ArithmeticException e) {
            System.out.println("Cannot compute: " + e.getMessage());
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("No value at position " + index);
        }
    }

    public static void main(String[] args) {
        String[] values = {"100", "abc"};
        divide(values, 0, 4);
        divide(values, 0, 0);
        divide(values, 1, 4);
        divide(values, 5, 4);
    }
}

Real output:

text
25
Cannot compute: / by zero
Cannot compute: For input string: "abc"
No value at position 5

Three different failures are handled by two catch blocks. The first block covers two types with the same response (using e.getMessage() to show the reason), and the second handles a missing index with a different message. The three types are unrelated, so the order between these blocks does not matter here. A catch (RuntimeException e) placed first would cover all of them, and the compiler would reject the blocks below it.

A design note: the missing-index case is really a bug in the calling code (lesson 2). Catching it here is acceptable for a teaching example, but in real code you would check index < values.length before reading. Use a catch for problems you cannot prevent; use an if for problems you can.

Example 3: asking again until the input is valid (realistic)

java
import java.util.Scanner;

public class Retry {
    static double readAmount(Scanner in) {
        while (true) {
            System.out.print("Amount: ");
            String text = in.nextLine().trim();
            try {
                double amount = Double.parseDouble(text);
                if (amount > 0) {
                    return amount;
                }
                System.out.println("The amount must be above zero, not " + text + ".");
            } catch (NumberFormatException e) {
                System.out.println("'" + text + "' is not a number. Try again, for example 25.50.");
            }
        }
    }

    public static void main(String[] args) {
        Scanner in = new Scanner("abc\n-5\n12.5\n");
        double amount = readAmount(in);
        System.out.println("Accepted " + amount);
    }
}

Real output:

text
Amount: 'abc' is not a number. Try again, for example 25.50.
Amount: The amount must be above zero, not -5.
Amount: Accepted 12.5

Here the Scanner reads from a fixed text instead of the keyboard so that the run is repeatable; in the lab you will use System.in. The while (true) loop repeats until return leaves it. A bad number triggers the catch, which prints a helpful message and lets the loop ask again. A negative number is not an exception at all: we check it with an if, because we expect it and can test for it. This is the retry pattern the lab uses.

Backend Insight

A server handles many requests in a loop. A try/catch around the handling of one request means one bad request produces an error response and the loop goes on to the next. Without it, one malformed request can bring the whole service down.

In real code, a catch block usually does one of: recover (use a default, ask again), report (tell the user, write to a log), or translate (wrap it in another exception, lesson 5). Doing none of these, the empty catch, is the classic production bug: the failure vanishes, and a person spends hours finding why data is missing.

The "narrowest useful type" rule matters here too. A broad catch (Exception e) in a request loop can mask a bug in your own code as a "bad request", so the bug is never fixed.

Common Mistakes

Mistake 1: The empty catch

  • Incorrect: catch (NumberFormatException e) { }
  • Correct: print or log what happened, ask again, or let it propagate.

An empty block says "I know this can fail and I do not care", which is rarely the truth.

Mistake 2: Catching Exception out of habit

catch (Exception e) is easy to type and hard to defend. It catches bugs you never meant to handle. Name the specific exception you expect, and let the rest propagate.

Mistake 3: General type before specific

A general catch above a specific one makes the lower block unreachable, and the code does not compile. Order them from most specific to least.

Mistake 4: Using try/catch instead of a simple check

try { ... } catch (ArrayIndexOutOfBoundsException e) to find the end of an array is slower to read and misuses the mechanism. Use the loop condition. Exceptions are for unexpected situations, not normal control flow.

Mistake 5: Assuming finally is only for errors

finally also runs when everything works. If you put "show error" in it, the message appears for successes too.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. What does catch (NumberFormatException e) do when the text parses fine?

Show solution A1

A1. Nothing. The catch block is skipped and execution continues after the whole statement (after any finally).

A2. After a catch block finishes, where does execution continue?

Show solution A2

A2. After the whole try statement, once the finally block, if there is one, has run.

A3. Can a try have a finally without any catch? Can it have a catch without a finally?

Show solution A3

A3. Yes to both: a try needs at least one catch or a finally.

Level B: Application

B1. What is printed?

java
try {
    System.out.println("A");
    int x = Integer.parseInt("12a");
    System.out.println("B");
} catch (NumberFormatException e) {
    System.out.println("C");
} finally {
    System.out.println("D");
}
System.out.println("E");
Show solution B1

B1. A, C, D, E on separate lines. B is skipped because parseInt throws on the line before it. The catch prints C, finally prints D, and E follows.

B2. This does not compile. Explain why and fix it.

java
try {
    riskyCall();
} catch (Exception e) {
    System.out.println("failed");
} catch (IllegalArgumentException e) {
    System.out.println("bad argument");
}
Show solution B2

B2. IllegalArgumentException is a subclass of Exception, which is caught first, so the second block can never run and the compiler rejects it. Fix: put catch (IllegalArgumentException e) first and catch (Exception e) second, or better, drop the general one unless you really handle it.

B3. Write a method static int toQuantity(String text) that returns the parsed number, or 1 when the text is not a whole number, and prints a one-line message in that case.

Show solution B3

B3.

java
static int toQuantity(String text) {
    try {
        return Integer.parseInt(text.trim());
    } catch (NumberFormatException e) {
        System.out.println("'" + text + "' is not a whole number; using 1.");
        return 1;
    }
}

Level C: Challenge

C1. Write a loop that reads whole numbers from a Scanner built from "ten\n3\n" until it gets a valid one, printing a helpful message for each bad line, then prints the number.

Show solution C1

C1.

java
Scanner in = new Scanner("ten\n3\n");
while (true) {
    String line = in.nextLine().trim();
    try {
        int n = Integer.parseInt(line);
        System.out.println("Got " + n);
        break;
    } catch (NumberFormatException e) {
        System.out.println("'" + line + "' is not a whole number. Try again.");
    }
}

C2. A teammate catches Exception around ten lines of code and prints "invalid input". Give two concrete ways this could mislead the user or the team, and rewrite the plan in words.

Show solution C2

C2. Any bug among the ten lines (a null, a wrong index) is reported as "invalid input", sending the user to fix something that is not their fault and hiding a real defect. Also, the real message and trace are lost, so nobody can diagnose it. The plan: catch only NumberFormatException around the parse, tell the user which value was wrong, and let other exceptions propagate to be reported.

Mini Challenge

Write a small console program that asks for two whole numbers and divides the first by the second. It must (1) re-ask for either number if it is not a whole number, (2) report a helpful message if the second is zero and ask for the second again, (3) print "Calculation finished" from a finally block after each attempt, and (4) have no empty catch block. Use a Scanner on a fixed text for your first test, then System.in.

Check your work: three bad inputs in a row (letters, a decimal, zero) each produce a different clear message and the program survives all of them.

Summary & Next Lesson

Key takeaways

  • Put risky code in try; the plan goes in catch. When something is thrown, the rest of the try is skipped and the matching catch runs; then execution continues after the statement.
  • Catch the narrowest useful type. Never leave a catch block empty.
  • Several catch blocks are tried top to bottom: specific first, general last.
  • Multi-catch (A | B e) handles unrelated types the same way; the types may not be related by inheritance.
  • finally runs whether or not something failed, even with a return. It may not run if the JVM exits while the try code is running.
  • Use if for problems you can check in advance; use catch for ones you cannot.

Next lesson

You can now handle an exception thrown by someone else. The next lesson, "Throwing and declaring", covers the other side: raising an exception from your own methods with a helpful message, and declaring a checked exception with throws.

Self-check

  1. When an exception is thrown inside try, what happens to the remaining lines of the try?
  2. Why must a specific catch come before a general one?
  3. When would you use multi-catch?
  4. Does finally run when the try block ends with return?
  5. Why is catch (Exception e) { } a bad idea?
Show answers
  1. They are skipped; Java looks for a matching catch.
  2. Catches are checked in order, so a general type above would catch everything and the specific one could never run; the compiler rejects it.
  3. When two or more unrelated exception types need exactly the same response.
  4. Yes, it runs before the method actually returns.
  5. It catches every kind of exception, including bugs, and the empty block hides the failure completely.

References

  • Oracle, The Java Tutorials, The finally Block. Fetched. Supports when finally runs (normal end, exception, early return, break, continue) and that it may not run if the JVM exits during try or catch. The mention of System.exit is general Java knowledge, not stated on that page.
  • Oracle, The Java Tutorials, Catching and Handling Exceptions. Fetched. Supports the pipe syntax for multi-catch and that the catch parameter is implicitly final. The "alternatives cannot be related by subclassing" rule is shown by the compiler output captured above, not by that page.
  • All output and compiler messages above were produced with JDK 21.0.10.

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.