1. External resources

As a Java program runs, sometimes it interacts with entities outside the Java machine. For example, with files on disk. These entities are usually called external resources. Internal resources are the objects created inside the Java machine.

Typically, the interaction follows this scheme:

Try-with-resources statement

Tracking resources

The operating system rigorously keeps track of the resources available, and also controls shared access to them from different programs. For example, if one program changes a file, then another program cannot change (or delete) that file. This principle isn't limited to files, but they provide the most readily understandable example.

The operating system has functions (APIs) that allow a program to acquire and/or release resources. If a resource is busy, then only the program that acquired it can work with it. If a resource is free, then any program can acquire it.

Imagine that your office has shared coffee mugs. If someone takes a mug, then other people can no longer take it. But once the mug is used, washed, and put back in its place, then anyone can take it again. The situation with seats on a bus or subway is the same. If a seat is free, then anyone can take it. If a seat is occupied, then it is controlled by the person who took it.

Acquiring external resources.

Every time your Java program starts working with a file on disk, the Java machine asks the operating system for exclusive access to it. If the resource is free, then the Java machine acquires it.

But after you've finished working with the file, this resource (file) must be released, i.e. you need to notify the operating system that you no longer need it. If you do not do this, then the resource will continue to be held by your program.

The operating system maintains a list of resources occupied by each running program. If your program exceeds the assigned resource limit, then the operating system will no longer give you new resources.

The good news is that if your program terminates, all resources are automatically released (the operating system itself does this).

The bad news is that if you're writing a server application (and a lot of server applications are written in Java), your server needs to be able to run for days, weeks, and months without stopping. And if you open 100 files a day and don't close them, then in a couple of weeks your application will reach its resource limit and crash. That's falling far short of months of stable work.


2. close() method

Classes that use external resources have a special method for releasing them: close().

Below we provide an example of a program that writes something to a file and then closes the file when it is done, i.e. it frees up the operating system's resources. It looks something like this:

Code Note
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
The path to the file.
Get the file object: acquire the resource.
Write to the file
Close the file - release the resource

After working with a file (or other external resources), you have to call the close() method on the object linked to the external resource.

Exceptions

It all seems simple. But exceptions can occur as a program runs, and the external resource won't be released. And that is very bad.

To ensure that the close() method is always called, we need to wrap our code in a try-catch-finally block and add the close() method to the finally block. It will look something like this:

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

This code will not compile, because the output variable is declared inside the try {} block, and therefore is not visible in the finally block.

Let's fix it:

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

It's okay, but it won't work if an error occurs when we create the FileOutputStream object, and this could happen quite easily.

Let's fix it:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

There are still a few criticisms. First, if an error occurs when creating the FileOutputStream object, then the output variable will be null. This possibility must be accounted for in the finally block.

Second, the close() method is always called in the finally block, which means that it is not necessary in the try block. The final code will look like this:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output != null)
      output.close();
}

Even if we don't consider the catch block, which can be omitted, then our 3 lines of code become 10. But we basically just opened the file and wrote 1. A little cumbersome, don't you think?


3. try-with-resources

And here Java's creators decided to sprinkle some syntactic sugar on us. Starting with its 7th version, Java has a new try-with-resources statement.

It was created precisely to solve the problem with the mandatory call to the close() method. The general case looks quite simple:

try (ClassName name = new ClassName())
{
     Code that works with the name variable
}

This is another variation of the try statement. You need to add parentheses after the try keyword, and then create objects with external resources inside the parentheses. For each object in the parentheses, the compiler adds a finally section and a call to the close() method.

Below are two equivalent examples:

Long code Code with try-with-resources
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output != null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

The code using try-with-resources is much shorter and easier to read. And the less code we have, the less chance of making a typo or other error.

By the way, we can add catch and finally blocks to the try-with-resources statement. Or you can not add them if they aren't needed.



4. Several variables at the same time

By the way, you may often encounter a situation when you need to open several files at the same time. Let's say you are copying a file, so you need two objects: the file from which you are copying data and the file to which you are copying data.

In this case, the try-with-resources statement lets you create one but several objects in it. The code that creates the objects must be separated by semicolons. Here's the general appearance of this statement:

try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
   Code that works with the name and name2 variables
}

Example of copying files:

Long code Short code
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input != null)
      input.close();
   if (output != null)
      output.close();
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}

Well, what can we say here? try-with-resources is a wonderful thing!


5. AutoCloseable interface

But that's not all. The attentive reader will immediately begin to look for pitfalls that limits how this statement can be applied.

But how does the try-with-resources statement work if the class does not have a close() method? Well, suppose that nothing will be called. No method, no problem.

But how does the try-with-resources statement work if the class has several close() methods? And they need arguments to be passed to them? And the class doesn't have a close() method without parameters?

I hope you really asked yourself these questions, and perhaps still others.

To avoid such issues, Java's creators came up with a special interface called AutoCloseable, which has only one method — close(), which has no parameters.

They also added the restriction that only objects of classes that implement AutoCloseable can be declared as resources in a try-with-resources statement. As a result, such objects will always have a close() method with no parameters.

By the way, do you think it's possible for a try-with-resources statement to declare as a resource an object whose class has its own close() method without parameters but which does not implement AutoCloseable?

The bad news: The correct answer is no — the classes must implement the AutoCloseable interface.

The good news: Java has a lot of classes that implement this interface, so it's very likely that everything will work as it should.