Skip to content
Laviyx Learn

Try

Java course menu

Java Exceptions: try, catch, finally, throw, Checked vs Unchecked

Learn Java exception handling: try, catch and finally, multiple and multi-catch blocks, the exception hierarchy, checked vs unchecked exceptions, throw and throws, custom exceptions, try-with-resources, stack traces, and best practices.

Advanced16 min readUpdated
On this page

An exception is an object that signals that something went wrong: a file is missing, text is not a number, an index is out of range. Instead of crashing immediately, Java lets you catch exceptions and decide what to do, and lets methods throw them to report problems they cannot handle themselves.

try and catch#

Java
public class Main {
    public static void main(String[] args) {
        try {
            int[] values = {10, 20, 30};
            System.out.println(values[1]);
            System.out.println(values[5]);
            System.out.println("this line is skipped");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Error: " + e.getMessage());
        }
        System.out.println("program continues");
    }
}
Output
20
Error: Index 5 out of bounds for length 3
program continues

When an exception is thrown, the rest of the try block is skipped and the first matching catch runs. e.getMessage() describes the problem.

Several catch blocks and multi-catch#

Java
public class Main {
    static void process(String input) {
        try {
            int index = Integer.parseInt(input);
            int[] data = {10, 20, 30};
            System.out.println(data[index]);
        } catch (NumberFormatException e) {
            System.out.println(input + ": not a number");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(input + ": no such position");
        }
    }

    public static void main(String[] args) {
        process("1");
        process("7");
        process("x");
    }
}
Output
20
7: no such position
x: not a number

When several exceptions are handled the same way, catch them together with |:

Java
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
    System.out.println("Invalid input: " + e.getMessage());
}

Catch blocks are checked in order, so put specific exception types before general ones.

finally#

A finally block runs whether or not an exception occurred, which makes it the place for clean-up:

Java
public class Main {
    static void divide(int a, int b) {
        try {
            System.out.println("Result: " + a / b);
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero");
        } finally {
            System.out.println("done");
        }
    }

    public static void main(String[] args) {
        divide(10, 4);
        divide(1, 0);
    }
}
Output
Result: 2
done
Cannot divide by zero
done

For closing files and connections, try-with-resources (below) is better than finally.

The exception hierarchy#

All exceptions derive from Throwable:

  • Error: serious JVM problems such as OutOfMemoryError and StackOverflowError. Do not catch these in normal code.
  • Exception: problems a program may handle.
    • Checked exceptions: all Exception subclasses except RuntimeException. Examples: IOException, FileNotFoundException. The compiler forces you to handle or declare them.
    • Unchecked exceptions: RuntimeException and its subclasses, such as NullPointerException, IllegalArgumentException, ArithmeticException, IndexOutOfBoundsException and NumberFormatException. These usually indicate programming errors, and the compiler does not force you to handle them.

Checked exceptions and throws#

A method that can throw a checked exception must either catch it, or declare it with throws so that callers deal with it:

Java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class Main {
    static String readConfig(String fileName) throws IOException {
        return Files.readString(Path.of(fileName));   // may throw IOException
    }

    public static void main(String[] args) {
        try {
            System.out.println(readConfig("missing-config.txt"));
        } catch (IOException e) {
            System.out.println("Could not read the config: " + e.getClass().getSimpleName());
        }
    }
}
Output
Could not read the config: NoSuchFileException

Without throws IOException on readConfig, or a try/catch around the call in main, the code does not compile.

Throwing exceptions#

Use throw to signal a problem yourself. For invalid arguments, the standard choice is IllegalArgumentException; for an object in the wrong state, IllegalStateException:

Java
public class Main {
    static int parseAge(String text) {
        int age = Integer.parseInt(text.strip());
        if (age < 0 || age > 130) {
            throw new IllegalArgumentException("age must be 0-130, got " + age);
        }
        return age;
    }

    public static void main(String[] args) {
        for (String input : new String[] {"25", "abc", "200"}) {
            try {
                int age = parseAge(input);
                System.out.println("age " + age);
            } catch (NumberFormatException e) {
                System.out.println(input + ": not a number");
            } catch (IllegalArgumentException e) {
                System.out.println(input + ": " + e.getMessage());
            }
        }
    }
}
Output
age 25
abc: not a number
200: age must be 0-130, got 200

NumberFormatException is a subclass of IllegalArgumentException, so it must be caught first; otherwise the general block would catch it.

Custom exceptions#

Create your own exception type by extending Exception (checked) or RuntimeException (unchecked):

Java
class InsufficientFundsException extends RuntimeException {
    private final double shortfall;

    InsufficientFundsException(double balance, double requested) {
        super("cannot withdraw " + requested + ", balance is " + balance);
        this.shortfall = requested - balance;
    }

    double getShortfall() {
        return shortfall;
    }
}

class Account {
    private double balance;

    Account(double balance) {
        this.balance = balance;
    }

    void withdraw(double amount) {
        if (amount > balance) {
            throw new InsufficientFundsException(balance, amount);
        }
        balance -= amount;
    }
}

public class Main {
    public static void main(String[] args) {
        Account acc = new Account(500);
        try {
            acc.withdraw(200);
            acc.withdraw(800);
        } catch (InsufficientFundsException e) {
            System.out.println(e.getMessage() + " (short by " + e.getShortfall() + ")");
        }
    }
}
Output
cannot withdraw 800.0, balance is 300.0 (short by 500.0)

try-with-resources#

Objects that hold resources (files, network connections, database connections) implement AutoCloseable. Declaring them in the parentheses of a try closes them automatically, even if an exception occurs:

Java
class Connection implements AutoCloseable {
    private final String name;

    Connection(String name) {
        this.name = name;
        System.out.println("open " + name);
    }

    void query() {
        throw new IllegalStateException("query failed");
    }

    @Override
    public void close() {
        System.out.println("close " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        try (Connection db = new Connection("database")) {
            db.query();
        } catch (IllegalStateException e) {
            System.out.println("caught: " + e.getMessage());
        }
    }
}
Output
open database
close database
caught: query failed

The resource is closed before the catch block runs. You will use try-with-resources for every file in File Handling.

Stack traces#

An uncaught exception ends the program and prints a stack trace: the exception type, its message, and the chain of method calls that led to it, most recent first:

Text
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
    at java.base/java.lang.Integer.parseInt(Integer.java:661)
    at Main.parseAge(Main.java:3)
    at Main.main(Main.java:11)

Read it from the top: the first line of your code (Main.java:3) is usually where to look.

Best practices#

  • Catch specific exceptions, not Exception or Throwable.
  • Never swallow exceptions with an empty catch block; at least log them.
  • Use exceptions for exceptional situations, not for normal control flow.
  • Prefer unchecked exceptions for programming errors, and checked ones for recoverable conditions the caller must consider.
  • Use try-with-resources for anything that must be closed.

Common mistakes#

Practice#

Practice exercise

Write double safeAverage(String[] items) that converts each item with Double.parseDouble, skips invalid items (printing skipping <item>), and throws IllegalArgumentException("no valid numbers") if none are valid. Call it with {"4", "x", "8"} and with {"a", "b"}.

Show solution
Java
public class SafeAverage {
    static double safeAverage(String[] items) {
        double sum = 0;
        int count = 0;
        for (String item : items) {
            try {
                sum += Double.parseDouble(item);
                count++;
            } catch (NumberFormatException e) {
                System.out.println("skipping " + item);
            }
        }
        if (count == 0) {
            throw new IllegalArgumentException("no valid numbers");
        }
        return sum / count;
    }

    public static void main(String[] args) {
        System.out.println(safeAverage(new String[] {"4", "x", "8"}));
        try {
            safeAverage(new String[] {"a", "b"});
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}
Output
skipping x
6.0
skipping a
skipping b
Error: no valid numbers

Key takeaway#

Finished this lesson?