JV

Java Fundamentals

19 lessons

Progress0%
1. Introduction to Java
1What is Java?
2. Variables and Data Types
1Primitive Types
3. Control Flow
ConditionalsLoops
4. Methods
Defining MethodsMethod Overloading
5. Object-Oriented Programming
Classes and ObjectsInheritanceInterfaces and Abstract Classes
6. Collections
ArrayList and LinkedListHashMap and HashSet
7. Exception Handling
Checked & Unchecked Exceptionstry-with-resources & Custom Exceptions
8. Generics
Generic Classes & MethodsWildcards & Type Erasure
9. Modern Java
Lambdas & Functional InterfacesStream API & Optional
10. File I/O
java.nio.file APIBuffered I/O & try-with-resources
All Tutorials
JavaException Handling
Lesson 13 of 19 min
Chapter 7 · Lesson 2

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:

code
try (ResourceType r = new ResourceType()) {
    // use r
} // r.close() called automatically here

Multiple 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:

java
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:

java
} 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

try-with-resourcesjava
// 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 exception classesjava
// 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.

Exception chainingjava
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?

PreviousNext