try, catch and finally in Java

try guards a block, catch handles specific failures, finally always runs, and try with resources closes what you opened.

The basic form

try {
    int value = Integer.parseInt(input);
    System.out.println(100 / value);
} catch (NumberFormatException e) {
    System.out.println("That was not a number: " + e.getMessage());
} catch (ArithmeticException e) {
    System.out.println("Cannot divide by zero");
} finally {
    System.out.println("This always runs");
}
  • try encloses the code that might fail.
  • Each catch handles one type, and the first matching block runs.
  • finally runs whether or not an exception occurred.

Catch order matters

try {
    load();
} catch (IOException e) {          // specific first
    recover();
} catch (Exception e) {            // general second
    report(e);
}

// Reversed, the compiler rejects it: the IOException block would be unreachable

Multi catch

try {
    process(path);
} catch (IOException | IllegalArgumentException e) {
    logger.warning("Could not process: " + e.getMessage());
}

When two types deserve the same handling, list them together. The parameter is implicitly final, and its static type is the nearest common supertype.

finally

Connection connection = null;
try {
    connection = open();
    connection.execute(sql);
} catch (SQLException e) {
    logger.severe("Query failed");
} finally {
    if (connection != null) {
        connection.close();        // runs on success, on failure and on return
    }
}

finally runs even when the try block executes a return. The only cases where it does not run are a JVM exit through System.exit, or the thread or process being killed.

The finally return trap

static int surprising() {
    try {
        return 1;
    } finally {
        return 2;      // discards the value above, and swallows any exception
    }
}
// surprising() returns 2
Never return, break or throw from a finally block. It silently overrides the result and can hide an exception that was on its way out.

try with resources

try (BufferedReader reader = Files.newBufferedReader(path);
     BufferedWriter writer = Files.newBufferedWriter(output)) {

    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line.strip());
        writer.newLine();
    }
} catch (IOException e) {
    logger.severe("Copy failed: " + e.getMessage());
}

Any resource implementing AutoCloseable declared in the header is closed automatically, in reverse order of creation, whether the block succeeds or fails. This replaced the error prone finally pattern above and should be used for every closeable resource.

Suppressed exceptions

try (var resource = open()) {
    resource.use();          // throws A
}                            // close() throws B
// A propagates; B is attached and available from getSuppressed()

Before try with resources, an exception thrown by close() would replace the real failure. Now the original wins and the secondary one is preserved rather than lost.

Effectively final resources

BufferedReader reader = Files.newBufferedReader(path);
try (reader) {               // allowed since Java 9 for an effectively final variable
    return reader.readLine();
}

Nested try blocks

try {
    for (String row : rows) {
        try {
            process(row);
        } catch (IllegalArgumentException e) {
            logger.warning("Skipping row: " + row);   // continue with the next row
        }
    }
} catch (IOException e) {
    logger.severe("Could not read the source");       // abandon everything
}

The inner block handles a per item failure and keeps going; the outer one handles a failure that ends the whole operation. Choosing the right scope for a try block is most of the skill.

Catching and rethrowing

try {
    repository.save(note);
} catch (SQLException e) {
    throw new StorageException("Could not save note " + note.id(), e);   // keep the cause
}

Always pass the original exception as the cause. Dropping it loses the stack trace that explains what really happened.

Common mistakes

  • An empty catch block. A failure occurs, nothing is recorded, and the bug becomes invisible.
  • Catching Exception when only one type was expected.
  • Wrapping an exception without passing the cause.
  • Returning from finally.
  • Closing resources manually when try with resources would do it correctly.
  • Wrapping a try around a hundred lines, so it is unclear which one can fail.

Best practices

  • Keep the try block as small as the code that can actually fail.
  • Catch the most specific type that you can genuinely handle.
  • Use try with resources for anything closeable.
  • Log or rethrow, but never silently swallow.
  • Preserve the cause when wrapping.

Practice

  1. Predict the output of a method that returns 1 in try and 2 in finally.
  2. Rewrite a finally based file close as try with resources.
  3. Why will catch (Exception e) before catch (IOException e) not compile?
  4. Write a loop that skips bad rows but stops on a read failure.
  5. Show what getSuppressed() returns when both the body and close() throw.

Conclusion

Catch what you can handle, keep the guarded block small, use try with resources for anything that must be closed, and never let a finally block change the outcome.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.