Exceptions
Try-with-Resources
Automatic Cleanup
You open a file, read it, then must close it. Forgetting to close leaks resources. Try-with-resources automatically closes resources when done - even if exceptions occur. No more manual finally blocks for cleanup.
Basic syntax
Declare resources in try parentheses.
// Basic Try-with-Resources Syntax
public class BasicSyntax {
public static void main(String[] args) {
System.out.println("=== Basic Try-with-Resources ===\n");
// Simple example with custom resource
System.out.println("--- Simple Resource ---");
try (SimpleResource resource = new SimpleResource("myResource")) {
resource.doWork();
System.out.println("Work completed");
} // close() called automatically here
System.out.println("After try block\n");
// With catch block
System.out.println("--- With Catch Block ---");
boolean triggerError = ;
try (SimpleResource resource = new SimpleResource("errorResource")) {
resource.doWork();
if (triggerError) {
resource.causeError();
}
} catch (RuntimeException e) {
System.out.println("Caught: " + e.getMessage());
}
System.out.println("Resource was still closed!\n");
// With finally block
System.out.println("--- With Finally Block ---");
try (SimpleResource resource = new SimpleResource("finallyResource")) {
resource.doWork();
} finally {
System.out.println("Finally block runs AFTER close()");
}
// Comparison with old style
System.out.println("\n--- Old Style (Don't Do This) ---");
oldStyleExample();
System.out.println("\n=== Key Points ===");
System.out.println("""
1. Resource declared in try() parentheses
2. Must implement AutoCloseable
3. Automatically closed when try block exits
4. Closed even if exception occurs
5. catch and finally blocks are optional
6. close() called BEFORE finally block
""");
}
static void oldStyleExample() {
SimpleResource resource = null;
try {
resource = new SimpleResource("oldStyle");
resource.doWork();
} finally {
if (resource != null) {
resource.close();
}
}
}
}
// Simple AutoCloseable resource
class SimpleResource implements AutoCloseable {
private final String name;
private boolean closed = false;
public SimpleResource(String name) {
this.name = name;
System.out.println(" [" + name + "] Resource opened");
}
public void doWork() {
if (closed) throw new IllegalStateException("Resource is closed");
System.out.println(" [" + name + "] Doing work...");
}
public void causeError() {
throw new RuntimeException("Something went wrong!");
}
@Override
public void close() {
if (!closed) {
closed = true;
System.out.println(" [" + name + "] Resource closed");
}
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// Basic Try-with-Resources Syntax
public class BasicSyntax {
public static void main(String[] args) {
System.out.println("=== Basic Try-with-Resources ===\n");
// Simple example with custom resource
System.out.println("--- Simple Resource ---");
try (SimpleResource resource = new SimpleResource("myResource")) {
resource.doWork();
System.out.println("Work completed");
} // close() called automatically here
System.out.println("After try block\n");
// With catch block
System.out.println("--- With Catch Block ---");
boolean triggerError = ;
try (SimpleResource resource = new SimpleResource("errorResource")) {
resource.doWork();
if (triggerError) {
resource.causeError();
}
} catch (RuntimeException e) {
System.out.println("Caught: " + e.getMessage());
}
System.out.println("Resource was still closed!\n");
// With finally block
System.out.println("--- With Finally Block ---");
try (SimpleResource resource = new SimpleResource("finallyResource")) {
resource.doWork();
} finally {
System.out.println("Finally block runs AFTER close()");
}
// Comparison with old style
System.out.println("\n--- Old Style (Don't Do This) ---");
oldStyleExample();
System.out.println("\n=== Key Points ===");
System.out.println("""
1. Resource declared in try() parentheses
2. Must implement AutoCloseable
3. Automatically closed when try block exits
4. Closed even if exception occurs
5. catch and finally blocks are optional
6. close() called BEFORE finally block
""");
}
static void oldStyleExample() {
SimpleResource resource = null;
try {
resource = new SimpleResource("oldStyle");
resource.doWork();
} finally {
if (resource != null) {
resource.close();
}
}
}
}
// Simple AutoCloseable resource
class SimpleResource implements AutoCloseable {
private final String name;
private boolean closed = false;
public SimpleResource(String name) {
this.name = name;
System.out.println(" [" + name + "] Resource opened");
}
public void doWork() {
if (closed) throw new IllegalStateException("Resource is closed");
System.out.println(" [" + name + "] Doing work...");
}
public void causeError() {
throw new RuntimeException("Something went wrong!");
}
@Override
public void close() {
if (!closed) {
closed = true;
System.out.println(" [" + name + "] Resource closed");
}
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
try (Resource r = new Resource()) { } - automatically closed after block.
Multiple resources
Manage several resources at once.
// Managing Multiple Resources
public class MultipleResources {
public static void main(String[] args) {
System.out.println("=== Multiple Resources ===\n");
// Multiple resources - closed in reverse order
System.out.println("--- Multiple Resources (Reverse Close Order) ---");
try (
Resource r1 = new Resource("first");
Resource r2 = new Resource("second");
Resource r3 = new Resource("third")
) {
System.out.println("Using all resources...");
r1.use();
r2.use();
r3.use();
}
System.out.println();
// Chained resources
System.out.println("--- Chained Resources ---");
try (
OuterResource outer = new OuterResource("outer");
InnerResource inner = new InnerResource("inner", outer)
) {
inner.process();
}
System.out.println();
// One resource fails to open
System.out.println("--- When Opening Fails ---");
try (
Resource r1 = new Resource("good1");
Resource r2 = new FailingResource("failing");
Resource r3 = new Resource("good2")
) {
System.out.println("This won't execute");
} catch (RuntimeException e) {
System.out.println("Caught: " + e.getMessage());
}
System.out.println("\nNotice: Only successfully opened resources are closed.");
System.out.println();
// Exception during use
System.out.println("--- Exception During Use ---");
try (
Resource r1 = new Resource("A");
Resource r2 = new Resource("B");
Resource r3 = new Resource("C")
) {
r1.use();
throw new RuntimeException("Error during processing");
} catch (RuntimeException e) {
System.out.println("Caught: " + e.getMessage());
}
System.out.println("All resources were closed despite the exception.");
System.out.println("\n=== Key Points ===");
System.out.println("""
1. Declare multiple resources separated by semicolons
2. Resources closed in REVERSE order (LIFO)
3. If opening fails, previously opened resources are closed
4. All resources closed even if exception in try body
5. Useful for dependent resources (e.g., BufferedReader wraps FileReader)
""");
}
}
// Basic resource
class Resource implements AutoCloseable {
protected final String name;
public Resource(String name) {
this.name = name;
System.out.println(" → Opening: " + name);
}
public void use() {
System.out.println(" Using: " + name);
}
@Override
public void close() {
System.out.println(" ← Closing: " + name);
}
}
// Resource that fails during construction
class FailingResource extends Resource {
public FailingResource(String name) {
super(name);
throw new RuntimeException("Failed to open: " + name);
}
}
// Outer resource
class OuterResource implements AutoCloseable {
private final String name;
public OuterResource(String name) {
this.name = name;
System.out.println(" → Opening outer: " + name);
}
public String getData() {
return "data from " + name;
}
@Override
public void close() {
System.out.println(" ← Closing outer: " + name);
}
}
// Inner resource that depends on outer
class InnerResource implements AutoCloseable {
private final String name;
private final OuterResource outer;
public InnerResource(String name, OuterResource outer) {
this.name = name;
this.outer = outer;
System.out.println(" → Opening inner: " + name + " (wraps " + outer.getData() + ")");
}
public void process() {
System.out.println(" Processing with " + outer.getData());
}
@Override
public void close() {
System.out.println(" ← Closing inner: " + name);
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Resources closed in reverse order of declaration. All get closed.
Custom AutoCloseable
Make your own resources work with try-with-resources.
// Creating Custom AutoCloseable Classes
public class CustomAutocloseable {
public static void main(String[] args) {
System.out.println("=== Custom AutoCloseable Classes ===\n");
// Basic custom resource
System.out.println("--- Basic Custom Resource ---");
try (Timer timer = new Timer("operation")) {
timer.simulateWork(100);
System.out.println(" Operation completed");
}
// Resource with state
System.out.println("\n--- Stateful Resource ---");
try (Counter counter = new Counter()) {
counter.increment();
counter.increment();
counter.increment();
System.out.println(" Count: " + counter.getCount());
}
// Resource that can fail on close
System.out.println("\n--- Resource That May Fail on Close ---");
try (SafeResource resource = new SafeResource()) {
resource.process();
}
// Idempotent close
System.out.println("\n--- Idempotent Close ---");
try (IdempotentResource resource = new IdempotentResource()) {
resource.use();
resource.close();
resource.close();
// Will be called again automatically
}
// Resource with cleanup action
System.out.println("\n--- Resource with Cleanup Callback ---");
try (CallbackResource resource = new CallbackResource(
() -> System.out.println(" [Cleanup] Resources freed!"))) {
resource.process();
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. Implement AutoCloseable interface
2. close() should be idempotent (safe to call multiple times)
3. Don't throw exceptions in close() if possible
4. Track state to prevent use after close
5. Consider what cleanup is actually needed
""");
}
}
// Timer resource - measures elapsed time
class Timer implements AutoCloseable {
private final String operationName;
private long elapsedMs = 0;
public Timer(String operationName) {
this.operationName = operationName;
System.out.println(" [Timer] Started: " + operationName);
}
public void simulateWork(long millis) {
elapsedMs += millis;
}
@Override
public void close() {
System.out.println(" [Timer] " + operationName + " took " + elapsedMs + "ms");
}
}
// Counter resource - tracks count
class Counter implements AutoCloseable {
private int count = 0;
private boolean closed = false;
public void increment() {
checkNotClosed();
count++;
}
public int getCount() {
checkNotClosed();
return count;
}
private void checkNotClosed() {
if (closed) {
throw new IllegalStateException("Counter is closed");
}
}
@Override
public void close() {
if (!closed) {
System.out.println(" [Counter] Final count: " + count);
closed = true;
}
}
}
// Resource that handles close errors gracefully
class SafeResource implements AutoCloseable {
public void process() {
System.out.println(" [SafeResource] Processing...");
}
@Override
public void close() {
System.out.println(" [SafeResource] Closing...");
try {
// Simulate cleanup that might fail
performCleanup();
} catch (Exception e) {
// Log but don't rethrow
System.out.println(" [SafeResource] Cleanup warning: " + e.getMessage());
}
System.out.println(" [SafeResource] Closed (even if cleanup had issues)");
}
private void performCleanup() {
// In real code, this might fail
System.out.println(" [SafeResource] Cleanup succeeded");
}
}
// Resource with idempotent close
class IdempotentResource implements AutoCloseable {
private boolean closed = false;
private int closeCallCount = 0;
public void use() {
if (closed) throw new IllegalStateException("Already closed");
System.out.println(" [Idempotent] Using resource");
}
@Override
public void close() {
closeCallCount++;
System.out.println(" [Idempotent] close() called (call #" + closeCallCount + ")");
if (!closed) {
closed = true;
System.out.println(" [Idempotent] Actually closing resources");
} else {
System.out.println(" [Idempotent] Already closed, doing nothing");
}
}
}
// Resource with cleanup callback
class CallbackResource implements AutoCloseable {
private final Runnable cleanupAction;
public CallbackResource(Runnable cleanupAction) {
this.cleanupAction = cleanupAction;
System.out.println(" [Callback] Resource created");
}
public void process() {
System.out.println(" [Callback] Processing...");
}
@Override
public void close() {
System.out.println(" [Callback] Closing...");
cleanupAction.run();
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Implement AutoCloseable interface with close() method.
Suppressed exceptions
Handle exceptions during close.
// Understanding Suppressed Exceptions
public class SuppressedExceptions {
public static void main(String[] args) {
System.out.println("=== Suppressed Exceptions ===\n");
// When both try body and close() throw
System.out.println("--- Exception in Try Body AND close() ---");
try {
try (ThrowingResource resource = new ThrowingResource()) {
resource.doWork();
throw new RuntimeException("Error in try body");
}
} catch (Exception e) {
System.out.println("Primary exception: " + e.getMessage());
Throwable[] suppressed = e.getSuppressed();
System.out.println("Suppressed exceptions: " + suppressed.length);
for (Throwable t : suppressed) {
System.out.println(" - " + t.getMessage());
}
}
// Multiple resources, multiple close failures
System.out.println("\n--- Multiple Resources Failing to Close ---");
try {
try (
ThrowingResource r1 = new ThrowingResource("first");
ThrowingResource r2 = new ThrowingResource("second");
ThrowingResource r3 = new ThrowingResource("third")
) {
throw new RuntimeException("Main error");
}
} catch (Exception e) {
System.out.println("Primary: " + e.getMessage());
System.out.println("Suppressed:");
for (Throwable t : e.getSuppressed()) {
System.out.println(" - " + t.getMessage());
}
}
// Only close() throws (no exception in try body)
System.out.println("\n--- Only close() Throws ---");
try {
try (ThrowingResource resource = new ThrowingResource()) {
resource.doWork();
System.out.println(" Work completed successfully");
// No exception here
}
} catch (Exception e) {
System.out.println("Exception from close(): " + e.getMessage());
System.out.println("Suppressed: " + e.getSuppressed().length);
}
// Examining suppressed exceptions
System.out.println("\n--- Examining Suppressed Exceptions ---");
try {
try (ExamineResource resource = new ExamineResource()) {
throw new RuntimeException("Primary failure");
}
} catch (Exception e) {
printExceptionTree(e, 0);
}
// Manually adding suppressed exceptions
System.out.println("\n--- Manually Adding Suppressed ---");
Exception primary = new Exception("Primary error");
primary.addSuppressed(new Exception("Cleanup error 1"));
primary.addSuppressed(new Exception("Cleanup error 2"));
System.out.println("Primary: " + primary.getMessage());
for (Throwable t : primary.getSuppressed()) {
System.out.println(" Suppressed: " + t.getMessage());
}
System.out.println("\n=== Key Points ===");
System.out.println("""
1. If try body throws, that's the PRIMARY exception
2. If close() also throws, it's SUPPRESSED
3. Access suppressed via getSuppressed()
4. Multiple close() failures → multiple suppressed
5. If only close() throws, that becomes the primary
6. Suppressed exceptions preserve all error information
""");
}
static void printExceptionTree(Throwable t, int indent) {
String prefix = " ".repeat(indent);
System.out.println(prefix + "Exception: " + t.getClass().getSimpleName());
System.out.println(prefix + " Message: " + t.getMessage());
Throwable[] suppressed = t.getSuppressed();
if (suppressed.length > 0) {
System.out.println(prefix + " Suppressed:");
for (Throwable s : suppressed) {
printExceptionTree(s, indent + 2);
}
}
if (t.getCause() != null) {
System.out.println(prefix + " Caused by:");
printExceptionTree(t.getCause(), indent + 2);
}
}
}
// Resource that throws on close
class ThrowingResource implements AutoCloseable {
private final String name;
public ThrowingResource() {
this("default");
}
public ThrowingResource(String name) {
this.name = name;
System.out.println(" [" + name + "] Opened");
}
public void doWork() {
System.out.println(" [" + name + "] Working...");
}
@Override
public void close() {
System.out.println(" [" + name + "] Closing (will throw)...");
throw new RuntimeException("Error closing " + name);
}
}
// Resource for examining exception tree
class ExamineResource implements AutoCloseable {
public ExamineResource() {
System.out.println(" [ExamineResource] Opened");
}
@Override
public void close() {
System.out.println(" [ExamineResource] Closing...");
RuntimeException closeError = new RuntimeException("Close failed");
closeError.initCause(new RuntimeException("Underlying cause"));
throw closeError;
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
If close throws while handling another exception, it's "suppressed".
Java 9 enhancement
Use effectively final variables.
// Java 9+ Effectively Final Variables in Try-with-Resources
public class Java9Enhancement {
public static void main(String[] args) {
System.out.println("=== Java 9+ Try-with-Resources Enhancement ===\n");
// Java 7/8 style - must declare in try()
System.out.println("--- Java 7/8 Style ---");
java7Style();
// Java 9+ style - can use existing effectively final variable
System.out.println("\n--- Java 9+ Style ---");
java9Style();
// Multiple existing variables
System.out.println("\n--- Multiple Existing Variables (Java 9+) ---");
multipleExistingVariables();
// Mixed: some new, some existing
System.out.println("\n--- Mixed Style (Java 9+) ---");
mixedStyle();
// Why effectively final matters
System.out.println("\n--- Why 'Effectively Final' Matters ---");
whyEffectivelyFinal();
System.out.println("\n=== Key Points ===");
System.out.println("""
Java 9+ Enhancement:
1. Can use existing effectively final variables
2. Variable must be final or effectively final
3. Makes code cleaner when resource already exists
4. Can mix new declarations and existing variables
5. Useful with method parameters and factory methods
""");
}
static void java7Style() {
// Must declare in try parentheses
try (MyResource resource = new MyResource("java7")) {
resource.use();
}
// Can't do this in Java 7/8:
// MyResource existing = new MyResource("x");
// try (existing) { ... } // Compile error in Java 7/8
}
static void java9Style() {
// Create resource outside try
MyResource resource = new MyResource("java9");
// Use existing variable in try (Java 9+)
// Just reference the variable!
try (resource) {
resource.use();
}
// resource is now closed
}
static void multipleExistingVariables() {
// Create multiple resources
MyResource r1 = new MyResource("resource1");
MyResource r2 = new MyResource("resource2");
// Use both in try
// Semicolon separated
try (r1; r2) {
r1.use();
r2.use();
}
}
static void mixedStyle() {
// Existing resource
MyResource existing = new MyResource("existing");
// Mix new declaration with existing variable
try (
existing; // Existing variable
MyResource newOne = new MyResource("new") // New declaration
) {
existing.use();
newOne.use();
}
}
static void whyEffectivelyFinal() {
MyResource resource = new MyResource("demo");
// This would make it NOT effectively final:
// resource = new MyResource("other"); // Reassignment
// Because 'resource' is never reassigned, it's effectively final
try (resource) {
resource.use();
}
// This WON'T compile:
/*
MyResource mutable = new MyResource("a");
mutable = new MyResource("b"); // Reassignment makes it not effectively final
try (mutable) { // Compile error!
mutable.use();
}
*/
System.out.println(" 'resource' was effectively final, so it worked!");
}
// Useful with method parameters
static void processResource(MyResource resource) {
// Parameters are effectively final by default
try (resource) {
resource.use();
}
}
// Useful with factory methods
static void withFactory() {
// Factory creates resource
MyResource resource = createResource();
// Use it in try
try (resource) {
resource.use();
}
}
static MyResource createResource() {
return new MyResource("from factory");
}
}
// Resource class
class MyResource implements AutoCloseable {
private final String name;
public MyResource(String name) {
this.name = name;
System.out.println(" [" + name + "] Created");
}
public void use() {
System.out.println(" [" + name + "] Being used");
}
@Override
public void close() {
System.out.println(" [" + name + "] Closed");
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Java 9+: existing final/effectively final variables work in try-with-resources.
Exercise: Practical.java
Build a file processor with complete resource management