What you'll learn

  • try / catch / finally
  • Checked vs unchecked exceptions
  • try-with-resources for files and connections
  • Writing and wrapping custom exceptions

Things go wrong: a file is missing, a number is badly formatted, an API times out. Java signals these with exceptions — objects that describe the problem and travel up the call stack until something handles them. Good exception handling is what separates a script from a reliable integration.

try / catch / finally

try {
    int q = Integer.parseInt("abc");          // throws NumberFormatException
    System.out.println(q);
} catch (NumberFormatException e) {
    System.out.println("Not a number: " + e.getMessage());
} finally {
    System.out.println("Always runs");        // cleanup, logging
}

When an exception is thrown, Java jumps to the first matching catch. The finally block runs whether or not there was an error.

Checked vs unchecked exceptions

CheckedUnchecked
ExamplesIOException, SQLExceptionNullPointerException, IllegalArgumentException
ExtendsExceptionRuntimeException
Compiler forces you to handle or declare it?yesno
Typical meaningsomething outside your control faileda bug or invalid input

For checked exceptions, either catch them or declare them with throws:

static int parseQuantity(String text) throws InvalidOrderException { ... }

Throwing your own

if (q <= 0) {
    throw new InvalidOrderException("Quantity must be positive: " + q);
}

A custom exception with a domain name (InvalidOrderException, PartnerRejectedException) makes logs and error reports far clearer than a generic Exception:

static class InvalidOrderException extends Exception {
    InvalidOrderException(String message) { super(message); }
}

Wrapping: keep the cause

When you catch a low-level exception and throw a more meaningful one, attach the original as the cause so the stack trace still shows what really happened:

catch (NumberFormatException e) {
    throw new IllegalArgumentException("Bad quantity '" + text + "'", e);
}

try-with-resources

Files, readers, database connections and HTTP clients must be closed. try-with-resources closes them automatically — even when an exception is thrown:

try (BufferedReader reader = new BufferedReader(new FileReader("orders.csv"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    System.out.println("Could not read: " + e.getMessage());
}

Any class that implements AutoCloseable works here.

Habits that pay off

  • Catch what you can handle, at the level that knows what to do. Don’t catch everything everywhere.
  • Never swallow exceptions with an empty catch {} — at minimum, log them.
  • Fail fast on invalid input with IllegalArgumentException; validate in constructors.
  • Include context in messages: which order, which file, which value.

The full example

// Exceptions.java — try/catch/finally, try-with-resources and custom exceptions.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;

public class Exceptions {

    // A custom checked exception for our domain
    static class InvalidOrderException extends Exception {
        InvalidOrderException(String message) { super(message); }
    }

    static int parseQuantity(String text) throws InvalidOrderException {
        try {
            int q = Integer.parseInt(text.trim());
            if (q <= 0) {
                throw new InvalidOrderException("Quantity must be positive: " + q);
            }
            return q;
        } catch (NumberFormatException e) {
            // Wrap the low-level error in a meaningful one, keeping the cause
            InvalidOrderException ex = new InvalidOrderException("Not a number: '" + text + "'");
            ex.initCause(e);
            throw ex;
        }
    }

    public static void main(String[] args) {
        for (String input : new String[] {"24", "-3", "abc"}) {
            try {
                System.out.println("Quantity: " + parseQuantity(input));
            } catch (InvalidOrderException e) {
                System.out.println("Rejected: " + e.getMessage());
            } finally {
                System.out.println("  (checked '" + input + "')");
            }
        }

        // try-with-resources closes the reader automatically, even on errors
        String csv = "id,qty\n1,24\n2,10";
        try (BufferedReader reader = new BufferedReader(new StringReader(csv))) {
            reader.readLine();                      // skip the header
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Row -> " + line);
            }
        } catch (IOException e) {
            System.out.println("Could not read: " + e.getMessage());
        }
    }
}

Try it yourself

Write a method double safeDivide(double a, double b) that throws an IllegalArgumentException with a clear message when b is zero, and call it inside a try/catch that prints the message.

Show a solution
static double safeDivide(double a, double b) {
    if (b == 0) {
        throw new IllegalArgumentException("Cannot divide " + a + " by zero");
    }
    return a / b;
}

try {
    System.out.println(safeDivide(10, 0));
} catch (IllegalArgumentException e) {
    System.out.println("Error: " + e.getMessage());
}

Key takeaways

  • try/catch/finally handles errors; finally always runs.
  • Checked exceptions must be caught or declared; unchecked ones usually signal bugs.
  • Use custom exceptions with clear names, and keep the original cause when wrapping.
  • Use try-with-resources for anything that must be closed.

Example files