Exceptions and Error Handling
Understand checked and unchecked exceptions, try/catch/finally, try-with-resources, custom exceptions, propagation, and resilient error handling.
Exception Hierarchy and Checked vs Unchecked
Java exceptions derive from Throwable. Error generally represents serious JVM/runtime conditions. Exception represents conditions applications may handle; RuntimeException and subclasses are unchecked.
Checked exceptions must be caught or declared. Neither category should be chosen mechanically; decide based on whether the API contract benefits from requiring callers to explicitly handle a recoverable condition.
try {
Files.readString(Path.of("config.txt"));
} catch (IOException e) {
throw new ConfigurationException("Cannot read configuration", e);
}
- Checked exceptions are enforced by the compiler.
- Unchecked exceptions include many invalid-state and programming failures.
- Preserve the original cause when wrapping exceptions.
try, catch, finally, and Exception Propagation
Exceptions propagate up the call stack until a matching handler is found. finally is intended for cleanup, although try-with-resources is usually safer for resources.
try {
process();
} catch (IllegalArgumentException e) {
System.err.println("Invalid input: " + e.getMessage());
} finally {
auditCompletion();
}
Catch specific exceptions rather than catching Exception everywhere. If an exception cannot be handled meaningfully at the current layer, propagate it with useful context.
An empty catch block silently discards failures and makes production diagnosis extremely difficult.
- Catch exceptions where you can make a meaningful decision.
- Preserve context and causes when rethrowing.
- Do not use exceptions as normal high-frequency control flow.
Try-with-Resources
Resources such as files, sockets, and database statements often implement AutoCloseable. Try-with-resources guarantees closure at the end of the statement's scope.
try (BufferedReader reader =
Files.newBufferedReader(Path.of("app.log"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
throw new RuntimeException("Unable to read log", e);
}
If both the body and close() throw, Java preserves the close failure as a suppressed exception.
- Prefer try-with-resources for
AutoCloseableresources. - Resources close in reverse declaration order.
- Suppressed exceptions preserve cleanup failures without losing the original exception.
Custom Exceptions and Error Strategy
Domain-specific exceptions communicate failure meaning to callers.
public final class InsufficientBalanceException
extends RuntimeException {
public InsufficientBalanceException(String message) {
super(message);
}
}
At service boundaries, convert low-level failures into domain-level errors when that improves the API contract. Avoid leaking secrets, SQL statements with sensitive values, access tokens, or personal data into exception messages and logs.
- Exceptions are part of an API's behavioral contract.
- Wrap low-level failures when a higher-level abstraction has more meaning.
- Keep sensitive information out of exception messages and logs.