Checked, unchecked, and throwing
Exceptions, collections and modern Java11 min readLesson 2 of 6
2.1 Two families
Checked exceptions are checked by the compiler. A method that can throw one must declare it with throws or handle it with try/catch. IOException and SQLException are common examples.
Unchecked exceptions extend RuntimeException. The compiler does not require a declaration. NullPointerException, IllegalArgumentException, and ArithmeticException belong here.
The split is about recovery. A checked exception marks a condition the caller may be able to handle, such as a missing file. An unchecked exception usually marks a bug or a violated precondition.
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try {
readConfig();
} catch (IOException e) {
System.out.println("Failed: " + e.getMessage());
}
}
static void readConfig() throws IOException {
throw new IOException("Missing file");
}
}Output
Failed: Missing file
2.1.1 throw and throws
throw raises an exception object at a point in the code. throws appears in a method signature and lists the checked exceptions that may leave the method. The two words are related but do different jobs.
throw new IllegalArgumentException("Age cannot be negative"); fails fast when a precondition is broken. The message should name the value or rule that failed, without exposing secrets.
A method can declare several checked exceptions, separated by commas. The caller must handle them or pass the responsibility up the chain.
public class Main {
public static void main(String[] args) {
try {
setAge(-3);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
static void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
System.out.println("Age: " + age);
}
}Output
Age cannot be negative
2.1.2 Custom exceptions
Extend Exception for a checked custom exception, or RuntimeException for an unchecked one. A class name such as InsufficientFundsException tells the caller what went wrong.
A custom exception can carry data, such as the missing amount. Add a constructor that passes a message to super(...) and a getter for the extra field. Keep the class small.
Use a custom type when callers need to distinguish the failure from other exceptions. If a standard type already conveys the meaning, use it.
public class Main {
public static void main(String[] args) {
try {
withdraw(100, 150);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
}
}
static void withdraw(int balance, int amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException("Short by " + (amount - balance));
}
}
}
class InsufficientFundsException extends Exception {
InsufficientFundsException(String message) {
super(message);
}
}Output
Short by 50
2.1.3 try-with-resources
A resource that implements AutoCloseable can be declared in the parentheses of a try-with-resources statement. The JVM calls close() automatically when the block ends, including when an exception is thrown.
try (Scanner scanner = new Scanner(file)) { ... } closes the scanner without a finally block. Several resources can be declared in one try, separated by semicolons, and they close in reverse order.
The resource variable is scoped to the try block. Code outside the block cannot use it, which prevents work with a closed resource.
public class Main {
public static void main(String[] args) {
try (Session session = new Session()) {
session.use();
}
System.out.println("Closed");
}
}
class Session implements AutoCloseable {
void use() { System.out.println("Using"); }
@Override
public void close() { System.out.println("Closing"); }
}Output
Using Closing Closed
2.1.4 Validation and rethrow
Validate arguments at the start of a method and throw an unchecked exception for a broken contract. That keeps the error close to its cause, so a bad value does not travel through several calls.
A catch block can log and rethrow when the method cannot recover but the caller might. throw e; preserves the original exception. Wrapping it in another exception can add context, and the original becomes the cause.
Avoid catching Exception only to print a message. A broad catch hides the type and makes the caller's handling less precise.
public class Main {
public static void main(String[] args) {
try {
parse("12");
parse("oops");
} catch (NumberFormatException e) {
System.out.println("Stopped at bad input");
}
}
static int parse(String text) {
return Integer.parseInt(text);
}
}Output
Stopped at bad input
2.1.5 Practice
- Write a method that declares
throws Exception, throw from it, and handle the call inmain. - Throw
IllegalArgumentExceptionfrom a validation method and print the message in a catch. - Create a custom checked exception with a message and throw it when a withdrawal exceeds the balance.
- Create a class that implements
AutoCloseableand confirm that close runs after the try block. - Parse two strings in one try, let the second throw, and note that the statement after it never runs.
For step 4, print one line in the try block and one in close(). The order in the output shows the cleanup path.
