188

The Java 7 try-with-resources syntax (also known as ARM block (Automatic Resource Management)) is nice, short and straightforward when using only one AutoCloseable resource. However, I am not sure what is the correct idiom when I need to declare multiple resources that are dependent on each other, for example a FileWriter and a BufferedWriter that wraps it. Of course, this question concerns any case when some AutoCloseable resources are wrapped, not only these two specific classes.

I came up with the three following alternatives:

1)

The naive idiom I have seen is to declare only the top-level wrapper in the ARM-managed variable:

static void printToFile1(String text, File file) {
    try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
}

This is nice and short, but it is broken. Because the underlying FileWriter is not declared in a variable, it will never be closed directly in the generated finally block. It will be closed only through the close method of the wrapping BufferedWriter. The problem is, that if an exception is thrown from the bw's constructor, its close will not be called and therefore the underlying FileWriter will not be closed.

2)

static void printToFile2(String text, File file) {
    try (FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw)) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
}

Here, both the underlying and the wrapping resource are declared in the ARM-managed variables, so both of them will certainly be closed, but the underlying fw.close() will be called twice: not only directly, but also through the wrapping bw.close().

This should not be a problem for these two specific classes that both implement Closeable (which is a subtype of AutoCloseable), whose contract states that multiple calls to close are permitted:

Closes this stream and releases any system resources associated with it. If the stream is already closed then invoking this method has no effect.

However, in a general case, I can have resources that implement only AutoCloseable (and not Closeable), which doesn't guarantee that close can be called multiple times:

Note that unlike the close method of java.io.Closeable, this close method is not required to be idempotent. In other words, calling this close method more than once may have some visible side effect, unlike Closeable.close which is required to have no effect if called more than once. However, implementers of this interface are strongly encouraged to make their close methods idempotent.

3)

static void printToFile3(String text, File file) {
    try (FileWriter fw = new FileWriter(file)) {
        BufferedWriter bw = new BufferedWriter(fw);
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
}

This version should be theoretically correct, because only the fw represents a real resource that needs to be cleaned up. The bw doesn't itself hold any resource, it only delegates to the fw, so it should be sufficient to only close the underlying fw.

On the other hand, the syntax is a bit irregular and also, Eclipse issues a warning, which I believe is a false alarm, but it is still a warning that one has to deal with:

Resource leak: 'bw' is never closed


So, which approach to go for? Or have I missed some other idiom that is the correct one?

Natix
  • 14,017
  • 7
  • 54
  • 69
  • 1
    Your first example isn't really demonstrative. If FileWriter throws an exception, then the resource certainly isn't opened and need not to be closed. – nullpotent Sep 23 '12 at 14:08
  • 6
    Of course, if the underlying FileWriter's constructor throws an exception, it doesn't get even opened and everything is OK. What the 1st example is about, is what happens if the FileWriter gets created, but the BufferedWriter's constructor throws an exception. – Natix Sep 23 '12 at 14:26
  • 8
    Its worth noting that BufferedWriter will not throw an Exception. Is there an example you can think of where this question not pure academic. – Peter Lawrey Sep 23 '12 at 14:29
  • 12
    @PeterLawrey Yes, your're right that the BufferedWriter's constructor in this scenario most probably will not throw an exception, but as I've pointed out, this question concerns any decorator-style resources. But for example `public BufferedWriter(Writer out, int sz)` can throw an `IllegalArgumentException`. Also, I can extend BufferedWriter with a class that would throw something from its constructor or create a whatever custom wrapper that I need. – Natix Sep 23 '12 at 14:40
  • 6
    The `BufferedWriter` constructor can easily throw an exception. `OutOfMemoryError` is probably the most common one as it allocates a fair chunk of memory for the buffer (although may indicate you want to restart the entire process). / You need to `flush` your `BufferedWriter` if you don't close and want to keep the contents (generally **only** the non-exception case). `FileWriter` picks up whatever happens to be the "default" file encoding - it's better to be explicit. – Tom Hawtin - tackline Sep 23 '12 at 15:03
  • 12
    @Natix I wish all question in SO are as well researched and clear as this one is. I wish I could up vote this 100 times. – Geek Jul 22 '14 at 18:37

8 Answers8

82

Here's my take on the alternatives:

1)

try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
    bw.write(text);
}

For me, the best thing coming to Java from traditional C++ 15 years ago was that you could trust your program. Even if things are in the muck and going wrong, which they often do, I want the rest of the code to be on best behaviour and smelling of roses. Indeed, the BufferedWriter might throw an exception here. Running out of memory wouldn't be unusual, for instance. For other decorators, do you know which of the java.io wrapper classes throw a checked exception from their constructors? I don't. Doesn't do code understandability much good if you rely upon that sort of obscure knowledge.

Also there's the "destruction". If there is an error condition, then you probably don't want to be flushing rubbish to a file that needs deleting (code for that not shown). Although, of course, deleting the file is also another interesting operation to do as error handling.

Generally you want finally blocks to be as short and reliable as possible. Adding flushes does not help this goal. For many releases some of the buffering classes in the JDK had a bug where an exception from flush within close caused close on the decorated object not be called. Whilst that has been fixed for some time, expect it from other implementations.

2)

try (
    FileWriter fw = new FileWriter(file);
    BufferedWriter bw = new BufferedWriter(fw)
) {
    bw.write(text);
}

We're still flushing in the implicit finally block (now with repeated close - this gets worse as you add more decorators), but the construction is safe and we have to implicit finally blocks so even a failed flush doesn't prevent resource release.

3)

try (FileWriter fw = new FileWriter(file)) {
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write(text);
}

There's a bug here. Should be:

try (FileWriter fw = new FileWriter(file)) {
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write(text);
    bw.flush();
}

Some poorly implemented decorators are in fact resource and will need to be closed reliably. Also some streams may need to be closed in a particular way (perhaps they are doing compression and need to write bits to finish off, and can't just flush everything.

Verdict

Although 3 is a technically superior solution, software development reasons make 2 the better choice. However, try-with-resource is still an inadequate fix and you should stick with the Execute Around idiom, which should have a clearer syntax with closures in Java SE 8.

user1156544
  • 1,725
  • 2
  • 25
  • 51
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • 5
    In version 3, how do you know that bw doesn't require its close to be called? And even if you can be sure if it, isn't that also "obscure knowledge" like you mention for version 1? – TimK Mar 26 '14 at 23:18
  • 3
    "*software development reasons make 2 the better choice*" Can you explain this statement in more detail? – Duncan Jones Aug 26 '14 at 07:30
  • 8
    Can you give an example of the "Execute Around idiom with closures" – Markus May 27 '15 at 09:58
  • 2
    Can you explain what's "clearer syntax with closures in Java SE 8"? – petertc Sep 01 '15 at 05:26
  • 1
    Example of "Execute Around idiom" is here: http://stackoverflow.com/a/342016/258772 – mrts Feb 25 '17 at 15:17
23

The first style is the one suggested by Oracle. BufferedWriter doesn't throw checked exceptions, so if any exception is thrown, the program is not expected to recover from it, making resource recover mostly moot.

Mostly because it could happen in a thread, with the thread dieing but the program still continuing -- say, there was a temporary memory outage that wasn't long enough to seriously impair the rest of the program. It's a rather corner case, though, and if it happens often enough to make resource leak a problem, the try-with-resources is the least of your problems.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
5

Option 4

Change your resources to be Closeable, not AutoClosable if you can. The fact that the constructors can be chained implies it isn't unheard of to close the resource twice. (This was true before ARM too.) More on this below.

Option 5

Don't use ARM and code very carefully to ensure close() isn't called twice!

Option 6

Don't use ARM and have your finally close() calls in a try/catch themselves.

Why I don't think this problem is unique to ARM

In all these examples, the finally close() calls should be in a catch block. Left out for readability.

No good because fw can be closed twice. (which is fine for FileWriter but not in your hypothetial example):

FileWriter fw = null;
BufferedWriter bw = null;
try {
  fw = new FileWriter(file);
  bw = new BufferedWriter(fw);
  bw.write(text);
} finally {
  if ( fw != null ) fw.close();
  if ( bw != null ) bw.close();
}

No good because fw not closed if exception on constructing a BufferedWriter. (again, can't happen, but in your hypothetical example):

FileWriter fw = null;
BufferedWriter bw = null;
try {
  fw = new FileWriter(file);
  bw = new BufferedWriter(fw);
  bw.write(text);
} finally {
  if ( bw != null ) bw.close();
}
m_vitaly
  • 11,856
  • 5
  • 47
  • 63
Jeanne Boyarsky
  • 12,156
  • 2
  • 49
  • 59
3

To concur with earlier comments: simplest is (2) to use Closeable resources and declare them in order in the try-with-resources clause. If you only have AutoCloseable, you can wrap them in another (nested) class that just checks that close is only called once (Facade Pattern), e.g. by having private bool isClosed;. In practice even Oracle just (1) chains the constructors and doesn't correctly handle exceptions partway through the chain.

Alternatively, you can manually create a chained resource, using a static factory method; this encapsulates the chain, and handle cleanup if it fails part-way:

static BufferedWriter createBufferedWriterFromFile(File file)
  throws IOException {
  // If constructor throws an exception, no resource acquired, so no release required.
  FileWriter fileWriter = new FileWriter(file);
  try {
    return new BufferedWriter(fileWriter);  
  } catch (IOException newBufferedWriterException) {
    try {
      fileWriter.close();
    } catch (IOException closeException) {
      // Exceptions in cleanup code are secondary to exceptions in primary code (body of try),
      // as in try-with-resources.
      newBufferedWriterException.addSuppressed(closeException);
    }
    throw newBufferedWriterException;
  }
}

You can then use it as a single resource in a try-with-resources clause:

try (BufferedWriter writer = createBufferedWriterFromFile(file)) {
  // Work with writer.
}

The complexity comes from handling multiple exceptions; otherwise it's just "close resources that you've acquired so far". A common practice seems to be to first initialize the variable that holds the object that holds the resource to null (here fileWriter), and then include a null check in the cleanup, but that seems unnecessary: if the constructor fails, there's nothing to clean up, so we can just let that exception propagate, which simplifies the code a little.

You could probably do this generically:

static <T extends AutoCloseable, U extends AutoCloseable, V>
    T createChainedResource(V v) throws Exception {
  // If constructor throws an exception, no resource acquired, so no release required.
  U u = new U(v);
  try {
    return new T(u);  
  } catch (Exception newTException) {
    try {
      u.close();
    } catch (Exception closeException) {
      // Exceptions in cleanup code are secondary to exceptions in primary code (body of try),
      // as in try-with-resources.
      newTException.addSuppressed(closeException);
    }
    throw newTException;
  }
}

Similarly, you can chain three resources, etc.

As a mathematical aside, you could even chain three times by chaining two resources at a time, and it would be associative, meaning you would get the same object on success (because the constructors are associative), and same exceptions if there were a failure in any of the constructors. Assuming you added an S to the above chain (so you start with a V and end with an S, by applying U, T, and S in turn), you get the same either if you first chain S and T, then U, corresponding to (ST)U, or if you first chained T and U, then S, corresponding to S(TU). However, it would be clearer to just write out an explicit three-fold chain in a single factory function.

Nils von Barth
  • 3,239
  • 2
  • 26
  • 27
  • Am I gathering correctly that one still needs to use try-with-resource, as in `try (BufferedWriter writer = createChainedResource(file)) { /* work with writer */ }`? – ErikE Nov 19 '17 at 06:10
  • @ErikE Yes, you would still need to use try-with-resources, but you would only need to use a single function for the chained resource: the factory function _encapsulates_ the chaining. I've added an example of use; thanks! – Nils von Barth Nov 25 '17 at 04:25
2

Since your resources are nested, your try-with clauses should also be:

try (FileWriter fw=new FileWriter(file)) {
    try (BufferedWriter bw=new BufferedWriter(fw)) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
} catch (IOException ex) {
    // handle ex
}
poison
  • 189
  • 3
  • 5
    This is pretty much similar to my 2nd example. If no exception occurs, the FileWriter's `close` will be called twice. – Natix Sep 26 '12 at 15:55
2

I just wanted to build on Jeanne Boyarsky's suggestion of not using ARM but making sure the FileWriter is always closed exactly once. Don't think there are any problems here...

FileWriter fw = null;
BufferedWriter bw = null;
try {
    fw = new FileWriter(file);
    bw = new BufferedWriter(fw);
    bw.write(text);
} finally {
    if (bw != null) bw.close();
    else if (fw != null) fw.close();
}

I guess since ARM is just syntactic sugar, we can't always use it to replace finally blocks. Just like we can't always use a for-each loop to do something that is possible with iterators.

AmyGamy
  • 101
  • 7
  • 6
    If both your `try` and `finally` blocks throw exceptions, this construct will lose the first (and potentially more useful) one. – rxg Jun 11 '13 at 12:27
0

I would say don't use ARM and go on with Closeable. Use method like,

public void close(Closeable... closeables) {
    for (Closeable closeable: closeables) {
       try {
           closeable.close();
         } catch (IOException e) {
           // you can't much for this
          }
    }

}

Also you should consider calling close of BufferedWriter as it is not just delegating the close to FileWriter , but it does some cleanup like flushBuffer.

sakthisundar
  • 3,278
  • 3
  • 16
  • 29
0

My solution is to do a "extract method" refactoring, as following:

static AutoCloseable writeFileWriter(FileWriter fw, String txt) throws IOException{
    final BufferedWriter bw  = new BufferedWriter(fw);
    bw.write(txt);
    return new AutoCloseable(){

        @Override
        public void close() throws IOException {
            bw.flush();
        }

    };
}

printToFile can be written either

static void printToFile(String text, File file) {
    try (FileWriter fw = new FileWriter(file)) {
        AutoCloseable w = writeFileWriter(fw, text);
        w.close();
    } catch (Exception ex) {
        // handle ex
    }
}

or

static void printToFile(String text, File file) {
    try (FileWriter fw = new FileWriter(file);
        AutoCloseable w = writeFileWriter(fw, text)){

    } catch (Exception ex) {
        // handle ex
    }
}

For class lib designers, I will suggest them extend the AutoClosable interface with an additional method to suppress the close. In this case we can then manually control the close behavior.

For language designers, the lesson is that adding a new feature could mean adding a lot others. In this Java case, obviously ARM feature will work better with a resource ownership transfer mechanism.

UPDATE

Originally the code above requires @SuppressWarning since the BufferedWriter inside the function requires close().

As suggested by a comment, if flush() to be called before close the writer, we need to do so before any return (implicit or explicit) statements inside the try block. There is currently no way to ensure the caller doing this I think, so this must be documented for writeFileWriter.

UPDATE AGAIN

The above update makes @SuppressWarning unnecessary since it require the function to return the resource to the caller, so itself does not necessary being closed. Unfortunately, this pull us back to the beginning of the situation: the warning is now moved back to the caller side.

So to properly solve this, we need a customised AutoClosable that whenever it closes, the underline BufferedWriter shall be flush()ed. Actually, this shows us another way to bypass the warning, since the BufferWriter is never closed in either way.

Earth Engine
  • 10,048
  • 5
  • 48
  • 78
  • The warning has its meaning: Can we be sure here that the `bw` will actually write tha data out? It _is_ buffered afterall, so it only has to write to disk sometimes (when the buffer is full and/or on `flush()` and `close()` methods). I guess the `flush()` method should be called. But then there's no need to use the buffered writer, anyway, when we're writing it out immediatelly in one batch. And if you don't fix the code, you could end up with data written to the file in a wrong order, or even not written to the file at all. – Petr Janeček Dec 03 '13 at 11:39
  • If `flush()` is required to be called, it shall happen whenever before the caller decided to close the `FileWriter`. So this should happen in `printToFile` right before any `return`s in the try block. This shall not be a part of `writeFileWriter` and thus the warning is not about anything inside that function, but the caller of that function. If we have an annotation `@LiftWarningToCaller("wanrningXXX")` it will help this case and similar. – Earth Engine Dec 05 '13 at 01:36