1. Introduction
Working with files in Java (and not only!) is always working with external resources. When you open a file, the operating system allocates a “descriptor” — a special identifier that allows your program to read from and write to the file. The number of such descriptors is limited: if you don’t close files, your program can quickly consume all available resources and start throwing mysterious errors like "Too many open files".
Moreover, if a file isn’t closed, it may remain locked for other programs. For example, you opened a file for writing, forgot to close it, and now neither you nor anyone else can modify or delete it. A kind of “never-ending hostage situation” in the world of file systems.
Real-world example
FileInputStream fis = new FileInputStream("data.txt");
int b = fis.read();
// ... do something, then forgot fis.close()
If you don’t call the close() method, the file will remain “hanging” until the program finishes. In large applications, this can lead to resource leaks and even to application crashes.
2. The old way: finally + close()
Before Java 7, the classic way to guarantee closing a file looked like this:
FileInputStream fis = null;
try {
fis = new FileInputStream("data.txt");
// read the file
int b = fis.read();
// ...
} catch (IOException e) {
System.out.println("Error reading file: " + e.getMessage());
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
System.out.println("Error closing file: " + e.getMessage());
}
}
}
Drawbacks of this approach
- It’s easy to forget the finally block and end up with resource leaks.
- Lots of boilerplate, especially if there are multiple streams.
- If an exception occurs during closing, you also need to catch it separately.
- The code becomes bulky and less readable.
3. The modern approach: try-with-resources
Fortunately, Java 7 introduced syntax that solves these problems elegantly and automatically — try-with-resources.
What it looks like
try (FileInputStream fis = new FileInputStream("data.txt")) {
int b = fis.read();
// work with the file
} catch (IOException e) {
System.out.println("Error working with file: " + e.getMessage());
}
// fis is already closed automatically here!
The key idea: all resources declared in the parentheses after try are automatically closed after the block finishes — even if an exception occurs in the middle. No need to write finally, no need to catch separate closing errors — Java will handle it for you.
Which classes can you use in try-with-resources?
Any class that implements the AutoCloseable interface (or the older Closeable). This is almost all standard I/O streams: FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, Scanner, PrintWriter, and many others.
4. try-with-resources syntax: details and examples
Single resource
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// reader is closed automatically!
Multiple resources
You can declare multiple resources separated by semicolons:
try (
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
System.out.println("Error copying: " + e.getMessage());
}
// both streams are closed!
Close order: resources are closed in the reverse order of their declaration. writer.close() is called first, then reader.close(). This matters if one stream depends on another.
Using custom classes
If you’re writing your own class that works with resources, simply implement the AutoCloseable interface:
class MyResource implements AutoCloseable {
public void doSomething() {
System.out.println("Working with the resource!");
}
@Override
public void close() {
System.out.println("Resource closed!");
}
}
try (MyResource res = new MyResource()) {
res.doSomething();
}
// After exiting the block, it will print: "Resource closed!"
5. How it works: diagram
flowchart TD
A[Opening a resource in try-with-resources] --> B{Did an exception occur in the try block?}
B -- No --> C[Resource is closed automatically]
B -- Yes --> D[Resource is closed automatically]
D --> E[Exception is propagated]
C --> F[Program continues execution]
Conclusion: regardless of whether there was an error, the resource will always be closed!
6. Examples: rewriting code “the new way”
Before (old style):
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("input.txt"));
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
System.out.println("Error while closing: " + e.getMessage());
}
}
}
After (try-with-resources):
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// That's it, no finally!
7. What happens if an error occurs during closing?
Sometimes the close operation itself can throw an exception (for example, if the file suddenly disappears). In try-with-resources, such exceptions are not lost: if there was already an exception in the try block and a second one occurred while closing the resource, it will be added as a suppressed exception to the primary one. You can see this using the Throwable.getSuppressed() method.
Example
try (MyResource res = new MyResource()) {
throw new IOException("Error in try block");
} catch (IOException e) {
System.out.println("Primary error: " + e.getMessage());
for (Throwable suppressed : e.getSuppressed()) {
System.out.println("Suppressed exception: " + suppressed.getMessage());
}
}
8. Which classes support try-with-resources?
It’s simple: any class that implements the AutoCloseable interface. Here are just some of the standard ones:
| Class | Purpose |
|---|---|
|
Read bytes from a file |
|
Write bytes to a file |
|
Read/write text |
|
Buffering streams |
|
Write formatted text |
|
Read data from a file/console |
|
Serialization/deserialization |
|
Working with ZIP archives |
|
Network connections |
If you use third-party libraries, check the documentation: if there’s a close() method, the class most likely supports try-with-resources.
9. Tips and useful nuances
You can declare variables outside try (since Java 9): you can use already declared resources if they are final or effectively final:
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
try (reader) {
// ...
}
It’s not just for files: try-with-resources is handy for any resources: network connections, databases, any objects with a close() method.
Don’t ignore exceptions: even with try-with-resources, don’t forget to catch and handle exceptions — it isn’t a silver bullet, just a convenient way to avoid leaks.
Don’t close the resource manually inside try: you don’t need to — Java will do it for you! If you call close() manually and then the try block ends, there will be an attempt to close an already closed resource. That’s usually safe but can be confusing.
10. Common mistakes when using try-with-resources
Error No. 1: forgot to use try-with-resources at all. If you’re still writing finally { resource.close(); } — you’re either in 2011 or you haven’t read this lecture! Use the modern syntax.
Error No. 2: declared the resource outside try and just use it inside. This code will not close the resource automatically:
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
try {
// ... using reader
} finally {
// And here you forgot to close it!
}
Error No. 3: calling close() manually inside the try block. This isn’t critical, but it’s redundant and can lead to double closing. Just trust Java.
Error No. 4: catching only Exception and ignoring I/O specifics. It’s better to catch specific exceptions (FileNotFoundException, IOException) to give the user clear messages.
Error No. 5: not handling suppressed exceptions. If an error occurs while closing a resource, it may be suppressed. If you analyze errors, don’t forget about getSuppressed().
GO TO FULL VERSION