Robust programs must handle failure deliberately, and Java's checked exceptions force you to think about it.
When something goes wrong, Java code throws an exception object, which unwinds the call stack until a matching catch handles it. If nothing does, the thread dies with a stack trace — read it from the top: the first line says what happened, the first frame in your code says where.
The hierarchy matters:
Exceptionsubclasses (exceptRuntimeException) are checked: the compiler forces callers to catch them or declarethrows. Examples:IOException,SQLException— failures outside your control.RuntimeExceptionsubclasses are unchecked: programming errors or invalid input, such asNullPointerException,IllegalArgumentException,IllegalStateException.Error(e.g.OutOfMemoryError) signals JVM-level problems you normally don't catch.
Catch the most specific type you can actually handle; combine types with a multi-catch catch (IOException | ParseException e). Never swallow an exception with an empty catch block — at minimum log it.
When you translate a low-level exception into your own, pass the original as the cause (throw new ConfigException("bad config", e)) so the stack trace keeps the root cause.
finally runs whether or not an exception occurred. For resources such as files, sockets and database connections, prefer try-with-resources, which closes anything AutoCloseable automatically, in reverse order, even on failure:
try (var reader = Files.newBufferedReader(path)) {
return reader.readLine();
}Use exceptions for exceptional situations, not ordinary control flow; for "not found" results, an Optional return is often clearer.
Example
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class Errors {
static class ConfigException extends RuntimeException {
ConfigException(String msg, Throwable cause) { super(msg, cause); }
}
static int readPort(String text) {
try (var reader = new BufferedReader(new StringReader(text))) {
return Integer.parseInt(reader.readLine().strip());
} catch (IOException | NumberFormatException e) {
throw new ConfigException("invalid port: " + text, e); // keep the cause
}
}
public static void main(String[] args) {
System.out.println(readPort("8080")); // 8080
try {
readPort("eighty");
} catch (ConfigException e) { // invalid port: eighty <- NumberFormatException
System.out.println(e.getMessage() + " <- " + e.getCause().getClass().getSimpleName());
}
}
}Remember
- Checked exceptions must be caught or declared; unchecked ones signal bugs or bad input
- Catch specific types, never swallow silently, and chain the cause when wrapping
- try-with-resources closes
AutoCloseableresources automatically
Watch out for
catch (Exception e) {}— hides real failures and makes debugging miserable.- Wrapping an exception without passing the cause, losing the original stack trace.