Exception Handling Best Practices

Basic Java
≈ 3 min read

Catch an exception where you can make a decision

A catch block by itself does not make error handling useful. An exception should be caught where the code actually knows what to do next: recover, choose another path, produce a clear result, or translate a technical error into one that better fits the current task.

If a method cannot handle the problem meaningfully, it is often better not to catch it just to make the “error disappear.” Let the exception propagate to a place where there is a clear response to it.

Catch specific exceptions

The more precise the type in a catch block is, the clearer it is which situation the code handles. A broad catch (Exception e) can easily capture errors for which the current block has no correct response at all.

try {
    int port = Integer.parseInt(value);
    System.out.println(port);
} catch (NumberFormatException e) {
    System.out.println("Invalid port: " + value);
}

Here the response applies specifically to an invalid numeric value. The code immediately shows which problem was expected and why this catch exists.

Do not swallow an error

An empty catch hides the fact that a failure happened: the program continues as if nothing went wrong. Because of that, the real cause of incorrect behavior is often discovered much later.

try {
    updateProfile();
} catch (ProfileValidationException e) {
    // Ошибка потеряна
}

If the exception can really be handled, handle it. If it cannot, let it propagate or translate it into another type while preserving the original cause. Ignoring an error should be a deliberate decision, not a way to get rid of an inconvenient catch.

Add context and preserve the cause

An exception message should help explain what exactly failed. A phrase such as Error provides almost no information, while the value the program was working with often explains the situation immediately.

try {
    return Integer.parseInt(value);
} catch (NumberFormatException e) {
    throw new InvalidPortException("Invalid port: " + value, e);
}

The new exception describes the meaning of the error at the program level, while the passed e preserves the original cause. This makes the context clearer without losing the exception chain.

Do not create a new exception without the cause when wrapping an exception that has already occurred. Otherwise, the original error disappears from the chain and investigating the problem becomes much harder.

Do not use exceptions as ordinary branching

If a situation can easily be checked with a regular condition and is a normal part of the scenario, do not deliberately let execution reach an exception just to move into a catch block.

if (index < 0 || index >= values.length) {
    return null;
}

return values[index];

This check directly shows the expected behavior. Exceptions are better reserved for situations that actually report a problem instead of being used as a replacement for a regular if.

Prefer try-with-resources for resources that must be closed

If an object implements AutoCloseable and must be closed after use, usually prefer the familiar try-with-resources over calling close() manually in finally. This keeps the code shorter and noticeably reduces the risk of forgetting to close the resource or losing an error during closing.