try-with-resources
On This Lesson 11 topics
Lesson Overview
Some things a program opens must be closed again: a file, a network connection, a database connection. If you forget, or if an exception jumps over the line that closes them, they leak: the operating system keeps them open, and a long-running program can eventually run out of them.
In the fire-alarm picture, this is the rule "last person out switches off the stove". It must happen whether the evening was calm or the alarm rang. You already know a tool for "must happen either way", the finally block. This lesson shows why it is awkward for this job, and a cleaner statement, try-with-resources, built for it.
Why this matters: backend services open files, connections, and streams all day. Resource leaks are a classic cause of services that run fine for hours and then fail. Try-with-resources is the standard solution you will see in nearly all modern Java code.
This lesson builds on try/catch/finally (lesson 3), throwing and declaring checked exceptions (lesson 4), and interfaces (BEG-024), because the feature depends on one.
A preview note. Files and paths have not been taught yet. We use just three calls: Path.of(...) to name a file, Files.writeString(...) to create one, and Files.newBufferedReader(...) to read one. Treat them as black boxes; the lab uses them the same way.
Learning Objectives
By the end of this lesson you will be able to:
- Explain what a resource leak is and why it matters.
- Write a try-with-resources statement for one or several resources.
- Explain what
AutoCloseableis and what the resource must implement. - State the order in which several resources are closed.
- Combine try-with-resources with
catchandfinally, and say when each runs. - Explain why it is safer than a manual
finallyplusclose().
Written Explanation
The old way: finally plus close
Here is how code had to close a file by hand:
static String firstLine(String path) throws IOException {
BufferedReader reader = null;
try {
reader = Files.newBufferedReader(Path.of(path));
return reader.readLine();
} finally {
if (reader != null) {
reader.close();
}
}
}It works, but it is clumsy: the variable must be declared outside the try, set to null, and checked again in finally. Worse, close() can itself throw an exception. The Oracle tutorial explains the trap: if both readLine and close throw, the exception from finally replaces the one from the try, so the original problem is lost. With several resources, one failing close() can even stop the next from being closed.
The new way
Put the resource in parentheses after try:
static String firstLine(String path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(Path.of(path))) {
return reader.readLine();
}
}When the block ends, Java calls close() for you, whether the block finished normally, threw an exception, or left with return. The Oracle tutorial describes it as closing each resource when the statement ends, normally or abruptly. There is no null check, no finally, and the variable reader exists only inside the statement, so nobody can use it after it is closed.
What counts as a resource: AutoCloseable
A resource is any object whose class implements the interface java.lang.AutoCloseable, which has one method, close(). All readers, writers, and streams in java.io qualify, because Closeable extends AutoCloseable. The two differ slightly: Closeable.close is declared to throw IOException, AutoCloseable.close is declared to throw Exception, and an implementing class may narrow that or throw nothing.
You can make your own class a resource by implementing the interface, as the first example below does. This is the interface idea from BEG-024 at work: any class that keeps the contract can be used with the statement.
Several resources
List them separated by semicolons:
try (BufferedReader in = Files.newBufferedReader(from);
BufferedWriter out = Files.newBufferedWriter(to)) {
// use in and out
}They are closed in reverse order of declaration: out first, then in. This is the sensible order, because a later resource may depend on an earlier one, as a stack of plates is cleared from the top.
catch and finally still work
You can add catch and finally blocks to a try-with-resources. The Oracle tutorial states that they run after the resources have been closed. So a catch sees a world where the files are already closed, which is exactly what you want:
- Resources are opened, in order.
- The block runs.
- Resources are closed, in reverse order.
- If something was thrown, the matching
catchruns. - Any
finallyruns.
Example 1 shows this sequence with real output.
Suppressed exceptions
What if the block throws and close() throws too? With the manual version, the close exception replaced the original. With try-with-resources, the block's exception is the one that propagates, and the exception from close() is attached to it as a suppressed exception. The Throwable documentation says suppressed exceptions are typically generated by this very statement, and getSuppressed() returns them. Nothing is silently lost, and the primary failure stays on top. Example 3 demonstrates it.
When to use it
Use it for anything that implements AutoCloseable and that you open yourself: files, streams, network sockets, database connections, scanners over a file. One caution: do not wrap System.in in a try-with-resources to read the keyboard, because closing it closes the standard input for the rest of the program. Create that Scanner once at the top and leave it open.
Examples
Example 1: see the order of events (simple)
A tiny resource that announces itself:
public class Order {
static class Tracer implements AutoCloseable {
private final String name;
Tracer(String name) {
this.name = name;
System.out.println("open " + name);
}
@Override
public void close() {
System.out.println("close " + name);
}
}
public static void main(String[] args) {
try (Tracer a = new Tracer("A"); Tracer b = new Tracer("B")) {
System.out.println("working");
throw new IllegalStateException("boom");
} catch (IllegalStateException e) {
System.out.println("caught " + e.getMessage());
} finally {
System.out.println("finally");
}
}
}Real output:
open A
open B
working
close B
close A
caught boom
finallyBoth resources were opened in order, working printed, and then the exception was thrown. Before the catch ran, B and A were closed in reverse order. Only then did caught and finally appear. Notice how little code guaranteed this. Our close() throws nothing, so no throws clause was needed.
Example 2: reading a file, and a missing one (practical)
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Read {
static void printFirstLine(Path path) {
try (BufferedReader reader = Files.newBufferedReader(path)) {
System.out.println("First line: " + reader.readLine());
} catch (IOException e) {
System.out.println("Could not read " + path + ": " + e);
}
}
public static void main(String[] args) throws IOException {
Path notes = Path.of("notes.txt");
Files.writeString(notes, "Buy milk\nCall the bank\n");
printFirstLine(notes);
printFirstLine(Path.of("missing.txt"));
}
}Real output (run in a folder where notes.txt did not exist before):
First line: Buy milk
Could not read missing.txt: java.nio.file.NoSuchFileException: missing.txtmain creates notes.txt first (hence throws IOException on main, a checked exception from lesson 2). The first call reads and closes the file. The second fails while opening, before any resource exists, so there is nothing to close and the catch runs. NoSuchFileException is a kind of IOException, so one catch handles both a missing file and a read failure, and the message names the path. A friendlier message for the user might say "File not found: missing.txt, check the name"; the lab asks for that.
Example 3: two resources, and what try-with-resources protects (realistic)
First, copying a file with two resources, one reading and one writing:
static void copy(Path from, Path to) throws IOException {
try (BufferedReader in = Files.newBufferedReader(from);
BufferedWriter out = Files.newBufferedWriter(to)) {
String line;
while ((line = in.readLine()) != null) {
out.write(line.toUpperCase());
out.newLine();
}
}
}Running it on the notes.txt from the previous example and printing copy.txt gave:
BUY MILK
CALL THE BANKBoth files were closed at the end. Closing the writer matters here, because it is what flushes buffered text to the file. Now compare the old and new styles when both the work and the closing fail:
static class Bad implements AutoCloseable {
@Override
public void close() {
throw new IllegalStateException("close failed");
}
}
static void oldStyle() {
Bad r = new Bad();
try {
throw new IllegalArgumentException("body failed");
} finally {
r.close();
}
}
static void newStyle() {
try (Bad r = new Bad()) {
throw new IllegalArgumentException("body failed");
}
}Catching the exception from each in main and printing its message and its suppressed exceptions, real output:
old: close failed, suppressed=0
new: body failed, suppressed=close failedIn the old style the real problem, "body failed", is gone and the caller sees only "close failed". In the new style the original exception propagates, and the close failure is recorded as suppressed. A person reading the log sees the real cause first. (Bad is a deliberate teaching device, because real close() methods fail only rarely.)
Backend Insight
A server may handle thousands of requests and each might open a file or a database connection. A leaked connection is not returned to the pool, and after enough requests the pool is empty and every new request waits or fails, even though the code "worked" in testing. Try-with-resources removes that class of bug: the close is tied to the block, so nobody has to remember it.
You will see the same shape with database connections, HTTP clients, and streams in later modules. The related idea, making cleanup automatic and tied to scope, appears in many languages under other names. When you meet a new class, check whether it is AutoCloseable (the API documentation lists it). If so, use it in a try-with-resources.
Common Mistakes
Mistake 1: Forgetting to close at all
- Incorrect:
BufferedReader r = Files.newBufferedReader(p);followed by reading, with no close. - Correct: open it in a try-with-resources statement.
Garbage collection does not reliably close resources at a moment you control, so do not depend on it.
Mistake 2: Closing in finally with no null check
If opening failed, the variable is still null and close() then throws NullPointerException, hiding the real problem. Try-with-resources removes the need for the check.
Mistake 3: Using the resource after the block
The variable is scoped to the statement. If you need data after the block, copy what you need into a variable declared outside (a String, a list) before the block ends.
Mistake 4: Wrapping System.in
Closing a Scanner over System.in closes the keyboard stream for the rest of the program. Keep that one open for the program's life.
Mistake 5: Catching the exception and doing nothing
A try-with-resources with catch (IOException e) { } is still a swallowed exception. Closing the file does not excuse hiding the failure.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. What is a resource leak? Give one example of a resource.
Show solution A1
A1. A resource leak is a resource (a file, connection, stream) that was opened and never closed, so the operating system keeps it in use. For a long-running program, enough leaks can exhaust the available handles.
A2. What interface must a class implement to be used in try-with-resources?
Show solution A2
A2. AutoCloseable (or its subinterface Closeable).
A3. Resources a and b are declared in that order. Which closes first?
Show solution A3
A3. b closes first, since resources close in reverse order of declaration.
Level B: Application
B1. Rewrite this with try-with-resources. Keep the checked exception handling:
BufferedReader reader = null;
try {
reader = Files.newBufferedReader(Path.of("data.txt"));
System.out.println(reader.readLine());
} catch (IOException e) {
System.out.println("Read failed: " + e.getMessage());
} finally {
if (reader != null) {
try { reader.close(); } catch (IOException e) { System.out.println("Close failed"); }
}
}Show solution B1
B1.
try (BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"))) {
System.out.println(reader.readLine());
} catch (IOException e) {
System.out.println("Read failed: " + e.getMessage());
}An IOException from close() now surfaces in the same catch (or as suppressed if the block also failed), so the nested try goes away.
B2. In which order do these appear: the catch block, the closing of the resource, the finally block? Why does the order help?
Show solution B2
B2. First the resource is closed, then the catch runs, then finally. The handler never runs while the file is still open, and finally runs after everything else is tidy.
B3. Write a class Timer that implements AutoCloseable and prints "start" when created and "stop" when closed. Use it in a try-with-resources that prints "work" in the block, and predict the output.
Show solution B3
B3.
static class Timer implements AutoCloseable {
Timer() { System.out.println("start"); }
@Override public void close() { System.out.println("stop"); }
}
// in main:
try (Timer t = new Timer()) {
System.out.println("work");
}Output: start, work, stop.
Level C: Challenge
C1. Write a method static int countLines(Path path) throws IOException using try-with-resources. Then write a main that calls it on an existing file and on a missing file, catching IOException and printing a clear, user-friendly message for each failure.
Show solution C1
C1.
static int countLines(Path path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path)) {
int count = 0;
while (reader.readLine() != null) {
count++;
}
return count;
}
}In main: try { System.out.println(countLines(Path.of("notes.txt"))); } catch (IOException e) { System.out.println("Could not count lines in notes.txt: " + e.getMessage()); }, then the same for missing.txt. For notes.txt from Example 2 the count is 2; for the missing file the message names the file.
C2. A teammate says: "I do not need try-with-resources because Java closes files when the program ends." Give two reasons this is not enough for a backend service.
Show solution C2
C2. First, a service runs for days, so unclosed handles accumulate long before it ends and can exhaust the limit. Second, buffered output may never be flushed to the file if the writer is not closed properly, so data can be lost.
Mini Challenge
Write a program that reads a small text file of numbers, one per line, and prints their sum. Use try-with-resources to read the file. If a line is not a whole number, print a message with the line number and the text, skip that line, and continue (use the narrow catch from lesson 3). If the file does not exist, print one clear message saying which file is missing, and stop. Test it with a good file, a file with one bad line, and a missing file.
Check your work: the file is always closed, no catch block is empty, and each failure gives a message naming the line or the file.
Summary & Next Lesson
Key takeaways
- Files, connections, and streams must be closed, or they leak.
- Put the resource in
try (...)and Java closes it for you, whether the block succeeds, throws, or returns. - The resource's class must implement
AutoCloseable(Closeableis a subtype). - Several resources are separated by
;and close in reverse order. catchandfinallyblocks run after the resources are closed.- If the block and
close()both throw, the block's exception propagates and the close failure is suppressed, so the real cause is not lost.
Next lesson
You now have every tool of the module: hierarchy, try/catch/finally, throw/throws, custom exceptions, and try-with-resources. The next lesson, "Demo: InsufficientFundsException end to end", puts the middle three into the bank Account and prepares the lab.
Self-check
- What does Java do at the end of a try-with-resources block?
- What must the resource's class implement?
- In what order are several resources closed?
- Does a
catchblock run before or after the resources are closed? - If the block and
close()both throw, which exception does the caller see?
Show answers
- It calls
close()on each resource, whether the block ended normally or with an exception. AutoCloseable(directly or throughCloseable).- In reverse order of declaration.
- After.
- The one from the block; the one from
close()is attached as suppressed.
References
- Oracle, The Java Tutorials, The try-with-resources Statement. Fetched. Supports
AutoCloseableandCloseable, closing on normal or abrupt completion, reverse closing order, suppressed exceptions, the failure mode of the oldfinallyapproach, and thatcatchandfinallyrun after the resources are closed. - Oracle, The Java Tutorials, The finally Block. Fetched. Supports the recommendation to use try-with-resources instead of
finallyfor closing resources. - Oracle, Java SE 21 API, Throwable. Fetched. Supports suppressed exceptions and
getSuppressed(). - All outputs above were produced with JDK 21.0.10. The advice about
System.in, and the point about buffered data being flushed on close, are general Java knowledge, not checked against a fetched page. The exercise solutions B3 and C1 were not run.
Practice
Apply this lesson with the module's lab, exercises and quiz.
Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.