Java Exception Handling
Exceptions are Java's mechanism for handling unexpected conditions: a file that does not exist, a network call that fails, a user input that does not parse. An exception is an object that carries information about what went wrong; the JVM unwinds the call stack until it finds a matching catch, or until the program terminates.
This tutorial covers the try/catch/finally syntax, the difference between checked and unchecked exceptions, when to throw and when to catch, how to write your own exception classes, and the modern try-with-resources form that closes resources automatically.
1. The Exception Hierarchy
All exceptions descend from java.lang.Throwable. There are two main branches:
Error— serious problems a program is unlikely to recover from (OutOfMemoryError,StackOverflowError). Catch them rarely, if ever.Exception— application-level problems. The subclassRuntimeExceptionis unchecked (compiler does not enforce handling); all otherExceptionsubclasses are checked (compiler requiresthrowsortry/catch).
| Exception | Type | Example cause |
|---|---|---|
NullPointerException | unchecked | Dereferencing a null reference |
IllegalArgumentException | unchecked | Bad method argument |
IndexOutOfBoundsException | unchecked | Array/list index out of range |
ArithmeticException | unchecked | Integer division by zero |
ClassCastException | unchecked | Invalid reference cast |
IOException | checked | File or network I/O failure |
SQLException | checked | Database error |
InterruptedException | checked | Thread was interrupted |
2. try / catch / finally
Wrap risky code in try; handle the failure in catch; finally runs whether or not an exception was thrown:
try {
riskyOperation();
} catch (IOException e) {
System.err.println("I/O failed: " + e.getMessage());
} catch (SQLException e) {
System.err.println("DB failed: " + e.getMessage());
} finally {
class=class="tok-str">"tok-cmt">// always runs - even if try returned or threw
cleanup();
}
class=class="tok-str">"tok-cmt">// try-finally without catch is legal but rarely what you want
try {
doWork();
} finally {
closeResources();
}
finally is for cleanup that must always run — closing files, releasing locks. Java 7+ added try-with-resources (below) which is now the preferred way to handle closeable resources.
3. Multi-Catch and Exception Chaining
One catch can handle several exception types with |:
try {
parseAndSave(input);
} catch (NumberFormatException | IllegalStateException e) {
class=class="tok-str">"tok-cmt">// handle both the same way
log.warn("bad input: {}", input, e);
throw new IllegalArgumentException("bad input", e);
}
If you catch a low-level exception and want to throw a higher-level one, use the original as the cause so you do not lose the stack trace:
class=class="tok-str">"tok-cmt">// chaining - rethrow with the original as the cause
try {
return Files.readString(Path.of(path));
} catch (IOException original) {
throw new RuntimeException("Failed to read " + path, original);
class=class="tok-str">"tok-cmt">// ^^^^^^^
class=class="tok-str">"tok-cmt">// original becomes the cause of the new exception
}
4. Throwing Exceptions
Use throw to raise an exception, throws to declare checked exceptions on a method:
public void withdraw(double amount) {
if (amount <= class="tok-num">0)
throw new IllegalArgumentException("amount must be positive");
if (amount > balance)
throw new IllegalStateException("insufficient funds");
balance -= amount;
}
class=class="tok-str">"tok-cmt">// declaring a checked exception
public String readFile(String path) throws IOException {
return Files.readString(Path.of(path));
}
class=class="tok-str">"tok-cmt">// caller must handle or declare
try {
String text = readFile("data.txt");
} catch (IOException e) {
e.printStackTrace();
}
Throw early: validate arguments at the top of a method. Throw specific types so callers can catch the right thing.
5. try-with-resources
Any object that implements AutoCloseable can be declared as a resource — the JVM calls close() automatically, even if an exception is thrown:
class=class="tok-str">"tok-cmt">// try-with-resources - resource closed automatically
try (var reader = Files.newBufferedReader(Path.of("data.txt"));
var writer = Files.newBufferedWriter(Path.of("out.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} class=class="tok-str">"tok-cmt">// reader.close() and writer.close() called here, even on exception
class=class="tok-str">"tok-cmt">// multiple resources - closed in reverse order of declaration
This eliminates an entire category of resource leaks. Always prefer it over manual close() in a finally block.
6. Custom Exceptions
Subclass RuntimeException for an unchecked exception, or Exception for a checked one:
class=class="tok-str">"tok-cmt">// unchecked - programming error
public class InvalidAgeException extends RuntimeException {
public InvalidAgeException(int age) {
super("Invalid age: " + age + " (must be class="tok-num">0-class="tok-num">150)");
}
}
class=class="tok-str">"tok-cmt">// checked - recoverable condition
public class ServiceUnavailableException extends Exception {
private final int statusCode;
public ServiceUnavailableException(int statusCode, String message) {
super(message);
this.statusCode = statusCode;
}
public int statusCode() { return statusCode; }
}
class=class="tok-str">"tok-cmt">// usage
public void setAge(int age) {
if (age < class="tok-num">0 || age > class="tok-num">150)
throw new InvalidAgeException(age);
this.age = age;
}
Prefer unchecked exceptions for programming errors (bad arguments, illegal state) and checked exceptions for conditions a caller can reasonably recover from (network timeout, file not found). The modern trend in the Java community is toward fewer checked exceptions — many libraries now wrap checked exceptions in unchecked ones to avoid forcing callers to handle them.
7. Best Practices
catch (Exception e) { /* nothing */ } is one of the worst things you can do. If you must ignore, at least log; if you cannot recover, rethrow wrapped in a runtime exception so the failure surfaces.
ThrowableThrowable includes Error — serious JVM-level problems you cannot recover from. Catching them masks real failures.
catch from most specific to leastThe first matching catch wins. catch (NullPointerException e) before catch (RuntimeException e); otherwise the specific catch is unreachable code and the compiler will complain.
Exercises
- Write a method that reads a file with
Files.readStringand prints its length. CatchIOExceptionand print a friendly message. - Rewrite the file-reading to use
try-with-resourceswith aBufferedReader. - Define a custom
InvalidAgeExceptionand throw it from a constructor when the age is negative. - Write a method that catches
NumberFormatExceptionand rethrows it wrapped in anIllegalArgumentExceptionwith the original as cause.