Search code examples
javatry-with-resourcesautocloseable

Catch errors during object construction from try-with-resources separately from body


Summary

I have a closeable type, CloseableClass that can throw an IOError in its constructor, methods and maybe even inside close. I want to use try-with-resources and still deal with errors during construction differently to errors during use (use includes cleanup). Even better, I would like to write maintainable code.


Let's say you wish to construct a closeable class instance and use it with a try-with-resources statement. It can throw IOException in both its constructor and the method used in the body of the try-with-resources:

import java.io.Closeable;
import java.io.IOException;
import java.util.Random;

public class CloseableClass implements Closeable {
    public CloseableClass() throws IOException {
        if (new Random().nextBoolean()) {
            throw new IOException();
        }
    }

    public void internetStuff() throws IOException {
        if (new Random().nextBoolean()) {
            throw new IOException();
        }
    }

    public void close() throws IOException {
        if (new Random().nextBoolean()) {
            throw new IOException();
        }
    }

    public static void main(String[] args) {
        try (CloseableClass closeable = new CloseableClass()) {
            closeable.internetStuff();
        }
        catch (IOException e) {
            System.out.println("Bad error!");
        }
    }
}

Let's say you want to deal with the errors thrown in the constructor and the body separately. Is there a supported way to do that? In Python I would do:

try:
    closeable = CloseableClass()
except IOException:
    print("Constructor error")
    return

try:
    with closeable:
        closeable.internet_stuff()
except IOException:
    print("Body error")

but in Java you cannot without assigning a second name to the object:

CloseableClass closeable_;

try {
    closeable_ = new CloseableClass();
}
catch (IOException e) {            
    System.out.println("Constructor error!");
    return;
}

try (CloseableClass closeable = closeable_) {
    closeable.internetStuff();
}
catch (IOException e) {
    System.out.println("Body error!");
}

I have been told that this is "unmaintainable code" primarily due to the use of closeable_, and I'm not far from agreeing. I wish to avoid using try-finally because then you have the even worse problem of emulating it:

CloseableClass closeable;

try {
    closeable = new CloseableClass();
}
catch (IOException e) {            
    System.out.println("Constructor error!");
    return;
}

try {
    closeable.internetStuff();
}
catch (IOException e) {
    try {
        closeable.close();
    }
    catch (IOException ignore) {
        // Already dealing with this
    }

    System.out.println("Body error!");
}
finally {
    try {
        closeable.close();
    }
    catch (IOException e) {
        System.out.println("Body error!");
    }
}

Note that this requires a second call to close to be a no-op, which the test class doesn't abide by (note that AutoCloseable doesn't require this, although Closeable does). This is a tad nicer when close can't throw, but not much.

Basically the problem is that

  • close can throw
  • Close before dealing with IOException to prevent printing "Body error!" twice
  • It's not obvious how to make it work with multiple initializers from the try-with-resources
  • You end up duplicating code anyway.

Am I just forced to live with the "unmaintainable code" or am I overlooking a good method to deal with this?


Solution

  • Since Java 9, try-with-resources has accepted ‘effectively final’ variables, so you do not need to reassign the variable.

    CloseableClass closeable;
    
    try {
        closeable = new CloseableClass();
    }
    catch (IOException e) {            
        System.out.println("Constructor error!");
        return;
    }
    
    try (closeable) {
        closeable.internetStuff();
    }
    catch (IOException e) {
        System.out.println("Body error!");
    }