Exception handling is how Java deals with things going wrong while a program runs: a file that isn't there, a network call that times out, an element that never appears. Instead of returning error codes, Java throws an exception object that travels up the call stack until some code catches it. This guide covers the exception hierarchy, try-catch-finally, try-with-resources, checked vs unchecked exceptions, reading stack traces and custom exceptions.

The Exception Hierarchy

Throwable
├── Error                      serious JVM problems: OutOfMemoryError, StackOverflowError (don't catch)
└── Exception                  problems a program can handle
    ├── IOException, SQLException, InterruptedException ...   (checked)
    └── RuntimeException                                         (unchecked)
        ├── NullPointerException, IllegalArgumentException
        ├── ArrayIndexOutOfBoundsException, NumberFormatException
        └── WebDriverException → NoSuchElementException, TimeoutException ...
Advertisement

try, catch and finally

try {
    int retries = Integer.parseInt(System.getProperty("retries", "2"));
    runSuite(retries);
} catch (NumberFormatException e) {
    System.err.println("retries must be a number: " + e.getMessage());
} finally {
    System.out.println("cleanup runs whether or not an exception was thrown");
}
  • The first catch whose type matches handles the exception, so list specific types before general ones.
  • One block can catch several unrelated types: catch (IOException | SQLException e).
  • finally runs after the try (and any catch), even if the code returns early. It doesn't run only if the JVM stops, for example with System.exit().

try-with-resources

Files, database connections and streams must be closed. Declaring them in the try (...) header closes them automatically, in reverse order, even when an exception is thrown:

try (Connection con = DriverManager.getConnection(url, user, pass);
     PreparedStatement ps = con.prepareStatement("SELECT status FROM orders WHERE id = ?")) {
    ps.setInt(1, orderId);
    try (ResultSet rs = ps.executeQuery()) {
        if (rs.next()) assertEquals(rs.getString("status"), "SHIPPED");
    }
}

Any class that implements AutoCloseable works here. It replaces most hand-written finally { x.close(); } blocks.

Checked vs Unchecked Exceptions

CheckedUnchecked
ExtendsException (not RuntimeException)RuntimeException
Compiler enforces handling?Yes: catch it or declare throwsNo
RepresentsConditions outside the program: missing files, network, databaseProgramming errors or invalid state: null, bad index, bad argument
ExamplesIOException, SQLException, InterruptedExceptionNullPointerException, IllegalStateException, every Selenium exception

Because all Selenium exceptions are unchecked, the compiler never forces you to handle NoSuchElementException. Handle it deliberately or let the test fail; see Selenium exceptions and fixes.

throw and throws

public String readConfig(String path) throws IOException {     // declares a checked exception
    if (path == null || path.isBlank()) {
        throw new IllegalArgumentException("config path is empty"); // throws one now
    }
    return Files.readString(Path.of(path));
}

throw raises an exception object; throws in the signature lists checked exceptions the method passes to its caller.

Reading a Stack Trace

org.openqa.selenium.TimeoutException: Expected condition failed: waiting for visibility of element located by By.id: welcome (tried for 10 second(s))
    at org.openqa.selenium.support.ui.WebDriverWait.timeoutException(WebDriverWait.java:84)
    ...
    at pages.HomePage.getWelcomeText(HomePage.java:27)
    at tests.LoginTest.validLogin(LoginTest.java:41)
Caused by: org.openqa.selenium.NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"#welcome"}
  1. The first line gives the exception type and message.
  2. Read down to the first line from your own code (here HomePage.java:27); framework lines above it are rarely the cause.
  3. A Caused by section shows the original exception that was wrapped. The deepest "Caused by" is usually the real problem.

In code, e.printStackTrace() prints it, but in a framework log it with your logger (log.error("Login failed", e)) so it lands in the report.

Custom Exceptions

public class FrameworkException extends RuntimeException {
    public FrameworkException(String message) { super(message); }
    public FrameworkException(String message, Throwable cause) { super(message, cause); }
}

// wrap a low-level error with context, keeping the cause
try {
    props.load(new FileInputStream(file));
} catch (IOException e) {
    throw new FrameworkException("Could not load config file: " + file, e);
}

Always pass the original exception as the cause, or the stack trace that explains the failure is lost.

Best Practices

  • Never leave a catch block empty; at minimum log the exception.
  • Catch the most specific type you can handle, not Exception or Throwable.
  • Don't use exceptions for normal flow; check with findElements(...).isEmpty() instead of catching NoSuchElementException.
  • Clean up with try-with-resources or finally; in TestNG use @AfterMethod(alwaysRun = true) to quit the browser.
  • Don't catch an exception in a test just to make it pass; a test should fail when the application is broken.

FAQs

What is the difference between checked and unchecked exceptions?

Checked exceptions must be caught or declared with throws, and the compiler enforces it; they extend Exception. Unchecked exceptions extend RuntimeException and aren't enforced, like NullPointerException.

Does finally run if there is a return in try?

Yes. The finally block runs before the method actually returns. It's skipped only if the JVM exits or the thread is killed.

What is the difference between throw and throws?

throw actually throws an exception object inside a method; throws in the method signature declares which checked exceptions the method may pass to its caller.

What is the difference between an Error and an Exception?

Both extend Throwable. Errors such as OutOfMemoryError signal serious JVM problems that applications shouldn't try to handle; exceptions signal conditions a program can reasonably catch.