Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I want to show you something that used to be a huge pain in the neck for all of us Java developers. We're going to look at how we handle "resources"—things like file handles, database connections, or network sockets—that the operating system expects us to close when we're done. If we don't, we get resource leaks, and eventually, the app crashes because it can't open any more files.

The "I'll just close it at the end" mistake

Let's start with a simple task: reading a small configuration file called app.properties. I'll write it the way a beginner might, just opening the stream and reading the line.

public void readConfig() {
    try {
        BufferedReader reader = new BufferedReader(new FileReader("app.properties"));
        System.out.println(reader.readLine());
        reader.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

At first glance, this looks fine. I open it, read it, and close it. But here's the problem: what happens if readLine() throws an exception? The execution jumps straight to the catch block. The reader.close() line is skipped entirely. The file stays open in the background. Do this a few thousand times in a production server, and your system will run out of file descriptors.

Fighting the Boilerplate

To fix that, the "old school" Java way was to use a finally block. The finally block is guaranteed to run regardless of whether an exception was thrown or not. Let's try that.

public void readConfig() {
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader("app.properties"));
        System.out.println(reader.readLine());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Stop and look at that. It's hideous. I had to declare the variable outside the try block so it was in scope for the finally block. Then, because close() itself can throw an IOException, I had to wrap the close call in another try-catch. This is what we call "boilerplate"—code that you have to write over and over again, but it doesn't actually add any business value. It just exists to satisfy the compiler.

Letting Java handle the cleanup

Around Java 7, the language designers realized we were all tired of writing this nonsense. They introduced try-with-resources. The trick is that you can actually declare and initialize your resource inside parentheses immediately after the try keyword.

Let's rewrite that same logic:

public void readConfig() {
    try (BufferedReader reader = new BufferedReader(new FileReader("app.properties"))) {
        System.out.println(reader.readLine());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

That's it. No finally block. No nested try-catches. The magic here is that Java automatically calls .close() on the reader the moment the try block finishes, whether it finished successfully or crashed with an exception.

You might be wondering: "How does Java know it's supposed to close this specific object?" The answer is the AutoCloseable interface. Any class that implements AutoCloseable (which almost every resource class in the JDK does) can be used in a try-with-resources statement. If you ever write your own class that manages a heavy resource—like a custom connection to a piece of hardware—make sure you implement AutoCloseable so your teammates can use this syntax too.

Handling multiple resources

One last thing: what if you need to read from one file and write to another? You don't need nested try-with-resources. You can just separate the resources with a semicolon inside those parentheses.

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) {
    e.printStackTrace();
}

Java will close these in the reverse order they were opened. It's clean, it's safe, and it keeps your code from looking like a pyramid of curly braces.




📋 Practical Task

Exercise: Secure Log File Archiver

You need to create a utility method that copies the contents of a log file (system.log) to a backup file (system.log.bak).

  • Write a method called archiveLog().
  • Use a try-with-resources block to open a BufferedReader for the source file and a BufferedWriter for the destination file.
  • Read the source file line by line and write each line to the backup file.
  • Ensure that you handle IOException properly.
  • Do NOT use a finally block to close the streams; let the try-with-resources handle it.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.