try-with-resources & Custom Exceptions
try-with-resources (Java 7+)
Managing resources like files, database connections, and network sockets used to require verbose try/catch/finally nesting. Java 7 introduced the try-with-resources statement to automate this.
Any object that implements java.lang.AutoCloseable (which declares void close() throws Exception) can be declared in the resource specification of a try block:
try (ResourceType r = new ResourceType()) {
// use r
} // r.close() called automatically hereMultiple resources can be listed separated by semicolons; they are closed in reverse declaration order, guaranteeing correct teardown. If both the try body and close() throw, the exception from close() is attached as a suppressed exception on the primary exception (accessible via Throwable.getSuppressed()).
Creating Custom Exception Classes
Standard exceptions are often too generic. Custom exceptions make APIs self-documenting and allow callers to catch exactly the error type they can handle:
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}Extend RuntimeException for unchecked custom exceptions, and Exception for checked ones. Always provide at least a String-message constructor and a cause-accepting constructor.
Exception Chaining
When you catch a low-level exception and throw a higher-level one, preserve the original as the cause so the full diagnostic information is retained:
} catch (SQLException e) {
throw new DataAccessException("query failed", e); // e is the cause
}Cause is set via the two-argument Throwable constructor or initCause(). Retrieve it with getCause().
Best Practices
- Catch the most specific type you can handle; let the rest propagate.
- Never swallow exceptions silently with an empty catch block.
- Log the full exception (including stack trace) before converting it.
- Use unchecked exceptions for programming errors and checked exceptions for recoverable conditions the caller can reasonably handle.
- Always include a meaningful message — it will be the first thing you read at 2 AM during an incident.
Code Examples
// A simple AutoCloseable resource for demonstration
class ManagedConnection implements AutoCloseable {
private final String name;
public ManagedConnection(String name) {
this.name = name;
System.out.println("Opening: " + name);
}
public void query(String sql) {
System.out.println("[" + name + "] query: " + sql);
}
@Override
public void close() {
System.out.println("Closing: " + name);
}
}
public class TryWithResourcesDemo {
public static void main(String[] args) {
// Single resource — close() is called automatically
try (ManagedConnection conn = new ManagedConnection("DB")) {
conn.query("SELECT 1");
}
System.out.println("After single resource block");
// Multiple resources — closed in reverse order (conn2 first, then conn1)
try (ManagedConnection conn1 = new ManagedConnection("Primary");
ManagedConnection conn2 = new ManagedConnection("Replica")) {
conn1.query("INSERT ...");
conn2.query("SELECT ...");
}
System.out.println("Both resources closed");
}
}try-with-resources guarantees close() is called even if an exception occurs. Multiple resources are closed in reverse declaration order — Replica before Primary.
// Custom checked exception
class InsufficientFundsException extends Exception {
private final double amount;
public InsufficientFundsException(double amount) {
super(String.format("Insufficient funds: need $%.2f more", amount));
this.amount = amount;
}
// Always provide a cause-chaining constructor
public InsufficientFundsException(double amount, Throwable cause) {
super(String.format("Insufficient funds: need $%.2f more", amount), cause);
this.amount = amount;
}
public double getAmount() { return amount; }
}
class BankAccount {
private double balance;
public BankAccount(double balance) { this.balance = balance; }
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(amount - balance);
}
balance -= amount;
System.out.printf("Withdrew $%.2f — balance: $%.2f%n", amount, balance);
}
}
public class CustomExceptionDemo {
public static void main(String[] args) {
BankAccount account = new BankAccount(100.0);
try {
account.withdraw(60.0);
account.withdraw(80.0); // triggers custom exception
} catch (InsufficientFundsException e) {
System.out.println("Error: " + e.getMessage());
System.out.printf("Short by: $%.2f%n", e.getAmount());
}
}
}Custom exceptions carry domain-specific data (the shortage amount here). The cause-accepting constructor is essential for exception chaining — always include it.
class DataAccessException extends RuntimeException {
public DataAccessException(String message, Throwable cause) {
super(message, cause); // cause is stored internally
}
}
class UserRepository {
// Simulates a low-level failure
private static void rawQuery(int id) {
if (id < 0) {
throw new IllegalArgumentException("negative id: " + id);
}
}
// Wraps low-level exception in a domain-level one
public String findById(int id) {
try {
rawQuery(id);
return "User#" + id;
} catch (IllegalArgumentException e) {
// Chain: preserve original as cause
throw new DataAccessException("Failed to find user " + id, e);
}
}
}
public class ExceptionChainingDemo {
public static void main(String[] args) {
UserRepository repo = new UserRepository();
// Successful call
System.out.println(repo.findById(42));
// Failed call — demonstrates chaining
try {
repo.findById(-1);
} catch (DataAccessException e) {
System.out.println("Domain error : " + e.getMessage());
System.out.println("Root cause : " + e.getCause().getMessage());
System.out.println("Cause class : " + e.getCause().getClass().getSimpleName());
}
// initCause() alternative (older style)
try {
RuntimeException wrapper = new RuntimeException("wrapper");
wrapper.initCause(new NullPointerException("inner npe"));
throw wrapper;
} catch (RuntimeException e) {
System.out.println("Via initCause: " + e.getCause().getMessage());
}
}
}Exception chaining preserves the original cause through the layers of abstraction. getCause() retrieves it, and printStackTrace() shows the entire causal chain — invaluable for debugging.
Quick Quiz
1. Which interface must a class implement to be used in a try-with-resources statement?
2. When multiple resources are declared in try-with-resources, in what order are they closed?
3. How do you retrieve the original cause from a chained exception?
4. Should you extend Exception or RuntimeException for a custom exception representing a programming bug (e.g., invalid argument)?
Was this lesson helpful?