Exceptions & Recovery

The Course
•
6 min read

An exception interrupts normal execution. A try block contains work that may fail; a matching catch block handles a particular exception type. After a catch finishes normally, the program continues after the try/catch. Without a matching handler, the run ends with a stack trace showing the exception and call locations.

In catch (NumberFormatException error), the first name is the type to handle and error is a variable holding the exception object. You can choose another variable name. error.getMessage() reads the explanation supplied when the exception was created. throw new IllegalArgumentException("...") creates an exception with that message and interrupts the normal path.

NumberFormatException is useful when parsing whole-number text. Catch it near the input boundary to explain the rejected value. Do not put all program code in one enormous catch(Exception) block: that can turn a real programming defect into an apparently ordinary input message. Recover only where you know what recovery means, such as skipping one malformed record while retaining earlier good ones.

Throw when an object cannot meet its contract

A constructor can throw IllegalArgumentException rather than create an invalid object. Check required values before assigning fields. The caller then decides whether to explain the problem, retry or stop. A constructor that throws never returns a successfully initialized instance to that caller.

Recover from a malformed number Java
public class Main {
    public static void main(String[] args) {
        try {
            int minutes = Integer.parseInt("oops");
            System.out.println(minutes);
        } catch (NumberFormatException error) {
            System.out.println("Enter a whole number");
        }

        System.out.println("Still running");
    }
}
Check your prediction
Enter a whole number
Still running

The print inside try is skipped because parsing throws first. The catch handles the known failure and the later message still runs. Replacing oops with 25 takes the normal path instead.

A validated constructor refuses bad data Java
public class Main {
    public static void main(String[] args) {
        try {
            Session session = new Session(0);
            System.out.println(session.minutes());
        } catch (IllegalArgumentException error) {
            System.out.println(error.getMessage());
        }
    }
}

class Session {
    private final int minutes;

    Session(int minutes) {
        if (minutes < 1 || minutes > 240) {
            throw new IllegalArgumentException("Minutes must be 1 to 240");
        }

        this.minutes = minutes;
    }

    int minutes() {
        return minutes;
    }
}
Check your prediction
Minutes must be 1 to 240

The variable session is never initialized successfully on this path. Throwing an exception is an explicit signal, not a returned sentinel such as -1 that could be mistaken for ordinary data.

Handle the more specific failure first

NumberFormatException is a more specific kind of IllegalArgumentException. If both catches follow the same try, put NumberFormatException first: Java tries the catches in order and chooses the first matching type. A broad catch first would already include number-format failures, making the later specific catch unreachable. You do not need to write an exception class to use this relationship.

Here parsing can fail before the range check, or a parsed number can fail the range rule. Follow each call through the try and just one catch.

Separate malformed text from a rejected duration Java
public class Main {
    public static void main(String[] args) {
        printDuration("oops");
        printDuration("0");
        printDuration("25");
    }

    static void printDuration(String text) {
        try {
            int minutes = Integer.parseInt(text);

            if (minutes < 1 || minutes > 240) {
                throw new IllegalArgumentException("Minutes must be 1 to 240");
            }

            System.out.println("Accepted: " + minutes);
        } catch (NumberFormatException error) {
            System.out.println("Not a whole number");
        } catch (IllegalArgumentException error) {
            System.out.println(error.getMessage());
        }
    }
}
Check your prediction
Not a whole number
Minutes must be 1 to 240
Accepted: 25

The first call reaches the first catch. The second reaches the second catch. The third reaches neither. Once a catch handles a failure, execution continues after the whole try/catch; it does not return to the failed statement inside try. The final planner uses this same distinction when explaining rejected input.

Your turn

Exercise

Explain rejected numeric input

Define printMinutes(String text). Parse trimmed text, reject values outside 1..240 with the message Out of range, and print valid minutes. Catch NumberFormatException and print Not a whole number. Assume text is non-null. Call it with “25”, “0” and “oops”.

Your workspace Java
public class Main {
    public static void main(String[] args) {
        // Call printMinutes on three cases.
    }

    // Define the helper and handle only the expected parsing exception.
}
Hint 1: the idea

Parsing failure and a parsed but invalid value need distinct paths. A guard can end the normal path after reporting invalidity.

Hint 2: a first step

Put parseInt inside try. Catch the specific exception and keep the range comparison in the successful parsing path.

Show one solution
public class Main {
    public static void main(String[] args) {
        printMinutes("25");
        printMinutes("0");
        printMinutes("oops");
    }

    static void printMinutes(String text) {
        try {
            int minutes = Integer.parseInt(text.trim());

            if (minutes < 1 || minutes > 240) {
                System.out.println("Out of range");
                return;
            }

            System.out.println(minutes);
        } catch (NumberFormatException error) {
            System.out.println("Not a whole number");
        }
    }
}
25
Out of range
Not a whole number

Change it and explain

Try whitespace around a valid number, a decimal such as “2.5”, and a number too large for int. Classify which failure is syntax/range at the parsing boundary and which is the session’s narrower domain rule.

For an uncaught error, start with the exception message and the first relevant program frame. The browser shows your source frames and may omit the JDK’s own internal frames. Do not compare exact internal stack-frame counts as part of a lesson’s business rule; focus on the failing operation and your caller chain.

Going further: finally

This cleanup feature is optional for the course project.

A finally block runs as control leaves the try/catch, whether the operation succeeded or failed in the ordinary exception cases we use here. Use it for cleanup when needed, not to disguise a failed operation as a success. We do not need resources or custom exception hierarchies to understand this basic flow.

Observe finally and the continuation Java
public class Main {
    public static void main(String[] args) {
        try {
            System.out.println(Integer.parseInt("bad"));
        } catch (NumberFormatException error) {
            System.out.println("Rejected");
        } finally {
            System.out.println("Attempt finished");
        }

        System.out.println("Next step");
    }
}
Check your prediction
Rejected
Attempt finished
Next step

Change bad to 12 and predict which line disappears. finally still runs, but the catch is no longer entered. This sequence is about control flow; it is not a claim that every error in every environment is recoverable.

Was this article helpful?

Your response is saved on this device.