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.

BasicSyntax.java
// 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.

try-with-resources Automatic resource management. Resource closed when block exits.

Multiple resources

Manage several resources at once.

MultipleResources.java
// 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.

CustomAutocloseable.java
// 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.

AutoCloseable Interface with `close()` method. Enables try-with-resources.

Suppressed exceptions

Handle exceptions during close.

SuppressedExceptions.java
// 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".

suppressed exception Secondary exception during cleanup. Retrieved via `getSuppressed()`.

Java 9 enhancement

Use effectively final variables.

Java9Enhancement.java
// 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