Concepts / Using try..catch Statements

Using try..catch Statements

A try..catch block separates risky statements from the response to a specific exception type.

  • Programming

When Text Is Not a Number

Many programs receive text from a user, a file, or a web form, and then need to treat part of that text as a number. The text might be a legal number such as 3.5, or it might be something like hello or 4.5.1 that cannot be read as a number at all. A program that simply assumes the text is valid will stop abruptly when it is not. A try..catch statement gives the program a way to attempt the risky conversion and respond to the failure in a controlled manner.

What do you think happens?

Suppose str holds the text abc. When Double.parseDouble is called on that text, which statements inside the try region run, and which run afterward?

  • Both output statements inside the try region run, then the catch region runs

  • The parse fails at the call, the rest of the try region is skipped, and the catch region runs

  • The catch region runs first, then the parse is attempted inside the try region

  • Nothing runs because the program stops before the try region is reached

Reveal answer
Answer:

The parse fails at the call, the rest of the try region is skipped, and the catch region runs

Double.parseDouble cannot read abc as a real number, so it throws NumberFormatException at the call itself. Control leaves the try region at that point, so any statement after the failing call inside the try region never runs. The catch region then runs in its place.

Splitting Code into Try and Catch Regions

A try..catch statement divides code into two regions. The try region holds the statements that might fail. In the program used in this article, those are the parsing call and the output that depends on it. The catch region names the kind of failure it is willing to handle, here NumberFormatException, and holds the response to that failure. The parentheses after catch declare a variable e that refers to the exception object. This example does not use e, but the declaration is required by the catch clause.

tryopens the risky regionx =Double.parseDouble(str);the call that may throwSystem.out.println ofxruns only if parsesucceededcatch(NumberFormatExceptione)the specific failurehandledx = Double.NaN;response to the failurecontainsthencontains
Which lines belong to the try section, which belong to the catch section, and how is the exception type tied to the catch clause?

Notice where the variable x is declared. The declaration double x; appears before the try region, not inside it. That placement allows both regions to assign to the same variable, so the value chosen in either path is still visible after the block finishes.

A Complete Guarded Conversion

The following program is an original illustration of the pattern described in this article. It declares str, declares x before the try region, attempts the parse inside the try region, and assigns Double.NaN in the catch region. Read it once from top to bottom, and ask for each value of str which of the two output statements inside the blocks will run.

Code walkthrough
public class ParseDemo {
    public static void main(String[] args) {
        String str = "3.5";
        double x;
        try {
            x = Double.parseDouble(str);
            System.out.println("Parsed value: " + x);
        } catch (NumberFormatException e) {
            x = Double.NaN;
            System.out.println("Input was not a number");
        }
        System.out.println("x is " + x);
    }
}
  1. 1
    String str holds the text to be converted.
  2. 2
    double x declares the numeric variable before any risky code runs.
  3. 3
    Inside the try region, Double.parseDouble attempts to read str as a double and assigns the result to x.
  4. 4
    When the parse succeeds, the next line prints the parsed value, and the catch region is skipped.
  5. 5
    After the block, the final line prints x, which holds 3.5 on this path.
  • double x; is declared before the try region so both paths can assign it
  • The parse call is the first statement in the try region and the point where failure can occur
  • The catch clause names NumberFormatException, the specific failure it handles
  • The output shown is for str holding the text 3.5, a legal real number
Output
Parsed value: 3.5
x is 3.5

Tracing Both Paths

Normal flow and failure flow are different paths through the same statements. Tracing the program means following one value of str through the code and noting each statement that actually runs. The two subsections below trace the two possible outcomes of the parse.

Why the Catch Block Assigns Double.NaN

After a failed parse there is no legitimate number to store in x. The program still needs x to hold something that later code can read. In Java, Double.NaN is a constant of type double that stands for not-a-number. It is a value a program can carry in a double variable to signal that no usable number was produced. The catch region therefore assigns Double.NaN so that the variable reflects the failure instead of holding a stale or misleading value.

Mistakes Beginners Make with Try and Catch

  • Common mistake

    Declaring the variable inside the try region

    What it looks like
    try { double x = Double.parseDouble(str); } catch (NumberFormatException e) { x = Double.NaN; }
    Why it fails

    x declared inside the try region is not visible in the catch region or after the block, so the catch assignment cannot refer to it.

    Do this instead

    Declare double x; before the try region so that both paths assign the same variable.

  • Common mistake

    Looking for the failure in the output statement

    Why it fails

    The output statement inside the try region only runs after the parse succeeds, so it cannot be the first point where the path diverged.

    Do this instead

    Trace back to the statement that can throw, here the parseDouble call, and ask whether it succeeds for the given input.

  • Common mistake

    Assuming statements after the failing call still run

    Why it fails

    Control leaves the try region at the point of failure, so the rest of the try region is skipped.

    Do this instead

    Mark the throwing statement as the exit point and treat everything after it inside the try region as skipped on the failure path.

  • Common mistake

    Catching a different exception type than the one the call throws

Practice Tracing a Guarded Conversion

MEDIUM

Suppose the program from this article is run with str holding the text 4.5.1. Before running it, state which print statements will execute, which statement is the exact point of divergence, and what value x holds at the end. Then explain in one sentence why Double.NaN is assigned.

Hints
  • Ask whether 4.5.1 can be read as a single real number by Double.parseDouble.
  • Find the first statement in the try region that can throw, and stop tracing the try region there on the failure path.
  • Recall that the catch region assigns Double.NaN when no usable number was produced.

Key Takeaways

  • A try..catch block separates the risky statements in the try region from the response to a specific exception type in the catch region.
  • When Double.parseDouble throws NumberFormatException, control leaves the try region at the failing call, skips the rest of that region, and runs the catch region.
  • The point of divergence is always the statement that can throw, so tracing starts by asking whether that call succeeds for the given input.
  • Declaring the variable before the try region lets both paths assign it, and Double.NaN marks that no usable number was produced.