Correct Way to Close Nested Streams and Writers in Java

Correct way to close nested streams and writers in Java

I usually do the following. First, define a template-method based class to deal with the try/catch mess

import java.io.Closeable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public abstract class AutoFileCloser {
// the core action code that the implementer wants to run
protected abstract void doWork() throws Throwable;

// track a list of closeable thingies to close when finished
private List<Closeable> closeables_ = new LinkedList<Closeable>();

// give the implementer a way to track things to close
// assumes this is called in order for nested closeables,
// inner-most to outer-most
protected final <T extends Closeable> T autoClose(T closeable) {
closeables_.add(0, closeable);
return closeable;
}

public AutoFileCloser() {
// a variable to track a "meaningful" exception, in case
// a close() throws an exception
Throwable pending = null;

try {
doWork(); // do the real work

} catch (Throwable throwable) {
pending = throwable;

} finally {
// close the watched streams
for (Closeable closeable : closeables_) {
if (closeable != null) {
try {
closeable.close();
} catch (Throwable throwable) {
if (pending == null) {
pending = throwable;
}
}
}
}

// if we had a pending exception, rethrow it
// this is necessary b/c the close can throw an
// exception, which would remove the pending
// status of any exception thrown in the try block
if (pending != null) {
if (pending instanceof RuntimeException) {
throw (RuntimeException) pending;
} else {
throw new RuntimeException(pending);
}
}
}
}
}

Note the "pending" exception -- this takes care of the case where an exception thrown during close would mask an exception we might really care about.

The finally tries to close from the outside of any decorated stream first, so if you had a BufferedWriter wrapping a FileWriter, we try to close the BuffereredWriter first, and if that fails, still try to close the FileWriter itself. (Note that the definition of Closeable calls for close() to ignore the call if the stream is already closed)

You can use the above class as follows:

try {
// ...

new AutoFileCloser() {
@Override protected void doWork() throws Throwable {
// declare variables for the readers and "watch" them
FileReader fileReader =
autoClose(fileReader = new FileReader("somefile"));
BufferedReader bufferedReader =
autoClose(bufferedReader = new BufferedReader(fileReader));

// ... do something with bufferedReader

// if you need more than one reader or writer
FileWriter fileWriter =
autoClose(fileWriter = new FileWriter("someOtherFile"));
BufferedWriter bufferedWriter =
autoClose(bufferedWriter = new BufferedWriter(fileWriter));

// ... do something with bufferedWriter
}
};

// .. other logic, maybe more AutoFileClosers

} catch (RuntimeException e) {
// report or log the exception
}

Using this approach you never have to worry about the try/catch/finally to deal with closing files again.

If this is too heavy for your use, at least think about following the try/catch and the "pending" variable approach it uses.

How to close nested I/O streams in Java

The docs of the close method of BufferedOutputStream (inherited from FilterOutputStream) says

The close method of FilterOutputStream calls its flush method, and then calls the close method of its underlying output stream.

So IntelliJ is clearly wrong. If in doubt trust the documentation, not the IDE's warnings - they are warnings and not errors for a reason.

That said, you should use a try-with-resources statement.

And you also do not need an intermediate variable, you can construct the FOS and immediately pass it as argument to the BOS constructor.

closing nested streams

When closing chained streams, you only need to close the outermost stream. Any errors will be propagated up the chain and be caught.

Take a look at here. Probably this question has been asked before.

Is it necessary to close each nested OutputStream and Writer separately?

Assuming all the streams get created okay, yes, just closing bw is fine with those stream implementations; but that's a big assumption.

I'd use try-with-resources (tutorial) so that any issues constructing the subsequent streams that throw exceptions don't leave the previous streams hanging, and so you don't have to rely on the stream implementation having the call to close the underlying stream:

try (
OutputStream outputStream = new FileOutputStream(createdFile);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
BufferedWriter bw = new BufferedWriter(osw)
) {
// ...
}

Note you no longer call close at all.

Important note: To have try-with-resources close them, you must assign the streams to variables as you open them, you cannot use nesting. If you use nesting, an exception during construction of one of the later streams (say, GZIPOutputStream) will leave any stream constructed by the nested calls inside it open. From JLS §14.20.3:

A try-with-resources statement is parameterized with variables (known as resources) that are initialized before execution of the try block and closed automatically, in the reverse order from which they were initialized, after execution of the try block.

Note the word "variables" (my emphasis).

E.g., don't do this:

// DON'T DO THIS
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new GZIPOutputStream(
new FileOutputStream(createdFile))))) {
// ...
}

...because an exception from the GZIPOutputStream(OutputStream) constructor (which says it may throw IOException, and writes a header to the underlying stream) would leave the FileOutputStream open. Since some resources have constructors that may throw and others don't, it's a good habit to just list them separately.

We can double-check our interpretation of that JLS section with this program:

public class Example {

private static class InnerMost implements AutoCloseable {
public InnerMost() throws Exception {
System.out.println("Constructing " + this.getClass().getName());
}

@Override
public void close() throws Exception {
System.out.println(this.getClass().getName() + " closed");
}
}

private static class Middle implements AutoCloseable {
private AutoCloseable c;

public Middle(AutoCloseable c) {
System.out.println("Constructing " + this.getClass().getName());
this.c = c;
}

@Override
public void close() throws Exception {
System.out.println(this.getClass().getName() + " closed");
c.close();
}
}

private static class OuterMost implements AutoCloseable {
private AutoCloseable c;

public OuterMost(AutoCloseable c) throws Exception {
System.out.println("Constructing " + this.getClass().getName());
throw new Exception(this.getClass().getName() + " failed");
}

@Override
public void close() throws Exception {
System.out.println(this.getClass().getName() + " closed");
c.close();
}
}

public static final void main(String[] args) {
// DON'T DO THIS
try (OuterMost om = new OuterMost(
new Middle(
new InnerMost()
)
)
) {
System.out.println("In try block");
}
catch (Exception e) {
System.out.println("In catch block");
}
finally {
System.out.println("In finally block");
}
System.out.println("At end of main");
}
}

...which has the output:


Constructing Example$InnerMost
Constructing Example$Middle
Constructing Example$OuterMost
In catch block
In finally block
At end of main

Note that there are no calls to close there.

If we fix main:

public static final void main(String[] args) {
try (
InnerMost im = new InnerMost();
Middle m = new Middle(im);
OuterMost om = new OuterMost(m)
) {
System.out.println("In try block");
}
catch (Exception e) {
System.out.println("In catch block");
}
finally {
System.out.println("In finally block");
}
System.out.println("At end of main");
}

then we get the appropriate close calls:


Constructing Example$InnerMost
Constructing Example$Middle
Constructing Example$OuterMost
Example$Middle closed
Example$InnerMost closed
Example$InnerMost closed
In catch block
In finally block
At end of main

(Yes, two calls to InnerMost#close is correct; one is from Middle, the other from try-with-resources.)

Closing a nested stream closes its parent streams too?

Yes, it does. Its Javadoc says:

Closes the ZIP output stream as well as the stream being filtered.

Also, the Javadoc for BufferedOutputStream says:

Closes this output stream and releases any system resources associated with the stream.

The close method of FilterOutputStream calls its flush method, and then calls the close method of its underlying output stream.

So when you close your ZipOutputStream, it will close your BufferedOutputStream, which will in turn close your FileOutputStream.

How to correctly close nested ZipInputStreams?

Thanks to the tip from @k314159 to use Apache's CloseShieldInputStream which is part of Apache Commons IO library I changed my code to:

try (ZipInputStream zis = new ZipInputStream(inputStream)) {
ZipEntry zipEntry;
while ((zipEntry = zis.getNextEntry()) != null) {
if (zipEntry.getName().endsWith(".zip")) {
try (CloseShieldInputStream cloned = CloseShieldInputStream.wrap(zis); ZipInputStream nestedZis = new ZipInputStream(cloned)) {
Pojo myPojo = myPojoReader.readFrom(nestedZis);
}
}
}
zis.closeEntry();
}

This preserves the functionality of my code while also passing Android's StrictMode verifications.

The CloseShieldInputStream is a proxy stream that prevents the underlying input stream from being closed.

Note There also is a CloseShieldOutputStream which I now use for the generating of the nested zip.

How many of these wrapped Java I/O objects do I have to close?

In general closing the outermost wrapper is enough; each wrapper closes the underlaying stream when it's closed.

It is possible to create a wrapper that doesn't do this, but that could be considered a bug since the documentation for FilterOutputStream requires that a call to close "releases any system resources associated with the stream."

Is there a way to close a Writer without closing the underlying stream?

Simply put, no. The way Java io stream classes are written, they always chain close operations. You could of course, create your own writer implementation that overrode this behavior.

What do I need to close when using PrintWriter in Java

You should use -

fileOut.close();

As you do not have any variable name assigned to BufferedWriter or FileWriter also the fileOut is made from them when you close fileOut it will in turn close both the streams.



Related Topics



Leave a reply



Submit