Learn / Programming / Java / Exception Handling

Java · Lesson 6 of 6

Exception Handling

try/catch/finally and throwing checked exceptions.

  • Intermediate
  • 13 min read
  • 3 objectives

Before this lessonLesson 5: Collections Framework

What you will learn

  • Catch NumberFormatException
  • Use finally
  • Throw IllegalArgumentException

When something goes wrong at runtime, Java throws an exception object and unwinds the call stack until some code catches it. If nothing does, the thread ends with a stack trace. Handling exceptions well is the difference between a program that crashes mysteriously and one that fails clearly.

When things go wrong

Files are missing, numbers arrive as text, networks fail. An exception is Java's way of saying "something went wrong here" and unwinding until some code decides how to handle it. Without handling, the program prints a stack trace and stops. With try/catch you keep control and respond sensibly, for example by showing a friendly message or trying again.

Checked and unchecked

  • Checked exceptions (such as IOException) describe problems outside your control. The compiler forces you to catch them or declare them with throws.
  • Unchecked exceptions extend RuntimeException (such as NullPointerException or IllegalArgumentException). They usually signal a bug, and you are not forced to catch them.

try / catch / finally

try {
    int n = Integer.parseInt("abc");
    System.out.println(n);
} catch (NumberFormatException e) {
    System.out.println("bad number: " + e.getMessage());
} finally {
    System.out.println("always runs");
}
Output
bad number: For input string: "abc"
always runs

List catch blocks from most specific to most general; the first match wins. You can also catch several types in one block with catch (IOException | SQLException e).

try-with-resources

Anything that must be closed (files, connections) should be opened in the parentheses of a try. Java closes it automatically, even when an exception is thrown, so you never need a manual finally for cleanup.

import java.io.*;
import java.nio.file.*;

try (BufferedReader in = Files.newBufferedReader(Path.of("notes.txt"))) {
    System.out.println(in.readLine());
} catch (IOException e) {
    System.out.println("cannot read file: " + e.getMessage());
}

Throwing your own

Validate input early and throw an exception whose message says exactly what was wrong. When you catch one exception and throw another, pass the original as the cause so the trace is not lost.

public static int parsePositive(String raw) {
    try {
        int n = Integer.parseInt(raw);
        if (n <= 0) throw new IllegalArgumentException("must be positive: " + n);
        return n;
    } catch (NumberFormatException e) {
        throw new IllegalArgumentException("not a number: " + raw, e);
    }
}

Catching a specific exception

public class Main {
    public static void main(String[] args) {
        String[] inputs = {"42", "abc", "7"};
        for (String s : inputs) {
            try {
                int n = Integer.parseInt(s);
                System.out.println("parsed " + n);
            } catch (NumberFormatException e) {
                System.out.println("not a number: " + s);
            } finally {
                System.out.println("(done with " + s + ")");
            }
        }
    }
}
Output
parsed 42
(done with 42)
not a number: abc
(done with abc)
parsed 7
(done with 7)

The loop keeps going after the bad input; the exception was contained. finally runs whether or not an exception happened, which is the right place for cleanup.

Reading a stack trace

An uncaught exception prints where it started and how the program got there. Read the first line (the type and message) and then the first line that mentions your code.

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Main.divide(Main.java:4)
	at Main.main(Main.java:9)

Throwing an exception with a clear message

public class Main {
    static double average(int[] nums) {
        if (nums.length == 0) {
            throw new IllegalArgumentException("need at least one number");
        }
        int sum = 0;
        for (int n : nums) sum += n;
        return (double) sum / nums.length;
    }

    public static void main(String[] args) {
        System.out.println(average(new int[]{2, 4, 6}));
        try {
            average(new int[]{});
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}
Output
4.0
Error: need at least one number

Checked versus unchecked, in plain words

  • Checked exceptions (such as IOException) are things the compiler forces you to deal with, because they can happen even in correct code (a file may be missing).
  • Unchecked exceptions (such as NullPointerException) usually mean a bug in the code; you fix the code rather than catch them everywhere.

Letting Java close resources for you

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    System.out.println(reader.readLine());
} catch (IOException e) {
    System.out.println("could not read file: " + e.getMessage());
}
// the reader is closed automatically, even if an exception was thrown

Common mistakes

  • Empty catch blocks (catch (Exception e) {}) hide failures. At least log the error.
  • Catching Exception or Throwable too broadly, which also swallows bugs.
  • Using exceptions for normal control flow; they are slow and obscure intent.
  • Losing the original cause by not passing it to the new exception.

Key takeaways

  • Wrap risky code in try and catch the specific exception you expect.
  • finally always runs; try-with-resources closes files and connections automatically.
  • Throw your own exceptions with clear messages when input is invalid.
  • Read stack traces top-down: type and message first, then the first line of your own code.
// Write your solution here
Course completeYou finished JavaReview the full course or pick your next one.