Try-with-resources

Java is widely known and respected for its automatic memory management. It makes the programs easier and faster to write because the developers does not have to manually track which objects are no longer used and can be freed or deleted from memory. In Java it is the responsibility of garbage collector (GC) to track unused objects and deallocate them.

However, in vast majority of applications there are more types of resources used than just memory – programs open files, connect to other applications via sockets or access databases via various IPC mechanisms. All of these resources are not managed by GC and are more scarce than memory. For example operating system may enforce limits on the number of concurrently opened file descriptors. Assuming that the limit is quite low (on my system it is 1024) and the application sometimes “forget” to close file, in short time the operating system may prevent the application from opening files any more because it reached the limit of opened file descriptors. The result is obvious – the application stops working and has to be restarted. This problem is most visible in applications that run for hours or days like servers or services. But even in small applications, one should still properly close resources.

How it should never be done

Let’s take a look at the first simple example how you should NEVER handle resources:

package com.example.blogtest;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class WriteFileWrong {

    private final static int bufferSize = 1024;
    private byte[] buffer = new byte[bufferSize];

    public void copyFile(String srcPath, String destPath) throws IOException {

        InputStream is = new FileInputStream(srcPath);
        OutputStream os = new FileOutputStream(destPath);
        while (true) {
            int readBytes = is.read(buffer);
            if (readBytes == -1) {
                break;
            }
            os.write(buffer, 0, readBytes);
        }
        is.close();
        os.close();
    }
}

First thing to notice is that we are pushing IOException up to the caller. It is fine and is a very common practice if we don’t know how to handle it. The problem in this example is that if constructor of FileOutputStream will throw IOException (quite likely), we will exit the method prematurely without closing first stream. The similar problem happens for read and write methods but the exception is less likely to appear.

Unfortunately, I often see such code in examples and books. What’s worse I even saw the code that does not even try to close the resource. It’s quite sad that they are teaching wrong practices even though doing it properly is not difficult.

Try-with-resource in Java 7

There are many ways to properly handle resource closing in Java but I would like to first show you the easiest one which was introduced in Java 7 – try-with-resources.

Try-with-resources construct automatically closes the resources, which were specified at the beginning of try block, after try block finishes (either normally or as a result of exception). There is no longer need to manually close the resource. The example should explain almost everything:

package com.example.blogtest;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class WriteFileTryWithResources {

    private final static int bufferSize = 1024;
    private byte[] buffer = new byte[bufferSize];

    public void copyFile(String srcPath, String destPath) throws IOException {

        try (InputStream is = new FileInputStream(srcPath);
             OutputStream os = new FileOutputStream(destPath)) {
            
            while (true) {
                int readBytes = is.read(buffer);
                if (readBytes == -1) {
                    break;
                }
                os.write(buffer, 0, readBytes);
            }
        } finally {
            // is and os streams will be closed automatically now even if exception occurs
        }
    }
}

If everything goes fine and no exception will be raised, is and os streams will be closed automatically as soon as the try block finishes.
If the exception is raised by FileOutputStream constructor, the execution will jump to finally block and the is stream, which was opened before, will be automatically closed.
If the exception is raised by read or write methods, the execution will jump to finally block and both is and os streams, which were opened before, will be automatically closed.

Requirements on resources in order to use them in try-with-resources

If you want to use the resource in try-with-resources block, it must implement AutoClosable interface. The definition of AutoClosable interface is pretty obvious:

package java.lang;

public interface AutoCloseable {
    void close() throws Exception;
}

If you take a look at AutoClosable you will notice that (almost?) all resources provided as part of Java 7 (including file streams, sockets and many more) implement this interface either directly or via Closable interface (which in turn extends AutoClosable interface). This means that we don’t have to worry about this unless we want to use our own class as resource.

Suppressed exception

If you take a close look at AutoClosable interface, you may notice that close() method may throw exception if an error appear while closing the resource. Because we cannot throw multiple exceptions at once in Java, you may wonder what happens to this exception or the original exception thrown in try block (if it occurs). Actually, the exception which appeared first will be thrown further and the rest of the exceptions will be attached to it and can be retrieved from it using Throwable.getSuppresed() method.

To visualize it let’s implement our own autoclosable resource:

package com.example.blogtest;

import java.io.IOException;

public class IntReader implements AutoCloseable {

    private int value;
    
    public IntReader(int value) {
        this.value = value;
    }

    public int readPositiveValue() throws IOException {
        if (value > 0)
            return value;
        else
            throw new IOException("Cannot read because value is not positive");
    }
    
    @Override
    public void close() throws Exception {
        if (value <= 0)
            throw new IOException("On close value was invalid");
    }
    
}

and use it in try-with-resources block:

package com.example.blogtest;

public class WriteFileOwnResource {
    
    public void readAndPrintExceptions() {
        try {
            read();
        } catch (Exception e) {
            System.out.printf("Exception occured: %s\n", e.getMessage());
            for (Throwable th : e.getSuppressed())
                System.out.printf("Suppressed exception: %s\n", th.getMessage());
        }
    }
    
    private void read() throws Exception {
        try (IntReader readerOk = new IntReader(10);
             IntReader readerFail = new IntReader(-1)) {
            readerOk.readPositiveValue();
            readerFail.readPositiveValue();
        }
    }
}

Object instance readerOk will not throw any exception because its value is positive but readerFail will throw first exception during call to readPositiveValue and another one when leaving the try block (when close() method will be automatically called on it). The first exception will be the main exception and the exception thrown by close() method will be available as suppressed exception. Therefore, the output of the code will be:

Exception occured: Cannot read because value is not positive
Suppressed exception: On close value was invalid
Advertisements

About Robert Piasecki

Husband and father, Java software developer, Linux and open-source fan.
This entry was posted in Java and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s