JDK-8219121 : ResourceBundle.getBundle can leave file open
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:i18n
  • Affected Version: 9,10,11,12,13
  • Priority: P3
  • Status: Resolved
  • Resolution: Won't Fix
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2019-02-15
  • Updated: 2019-09-18
  • Resolved: 2019-09-18
Related Reports
Relates :  
Description
A DESCRIPTION OF THE PROBLEM :
ResourceBundle.getBundle leaves the file open if the bundle is inside a jar file. This means the jar file cannot be deleted. I have a simple test program that demonstrates the problem -- it works fine with Java 8, but fails with Java 11 (and I tried early access 13). 

REGRESSION : Last worked in version 8u202

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the demo program with java 8 and with java 11, observe that the jar file is deleted ok when run with java 8, but cannot be deleted with java 11. 

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I expect the jar file to be deleted. 
ACTUAL -
The jar file is not deleted (because it is left open). 

---------- BEGIN SOURCE ----------
Here's the source, but it requires (1) commons-io just for doing a simple file copy, (2) a mysql jdbc driver jar file. 
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Locale;
import java.util.ResourceBundle;

import org.apache.commons.io.FileUtils;

/**
 * This demonstrates a bug in Java 11 ResourceBundle. 
 * If you run this program with Java 8, the file is indeed deleted. 
 * If you run with Java 11, the file is not deleted. Ditto early release of Java 13.
 */
public class ResourceBundleBug {
    private static final String FILE = "mysql-connector-java-5.1.35-bin.jar";

    public static void main(String[] args) throws IOException {
        FileUtils.copyFileToDirectory(new File("res/" + FILE), FileUtils.getTempDirectory());
        URL url = new URL("file:" + FileUtils.getTempDirectoryPath() + "/" + FILE);
        URLClassLoader cl = new URLClassLoader(new URL[] {url}, null);
        ResourceBundle rb = ResourceBundle.getBundle("com.mysql.jdbc.LocalizedErrorMessages", Locale.US, cl);
        cl.close();
        File file = new File(FileUtils.getTempDirectoryPath() + "/" + FILE);
        System.out.println(file + " exists: " + file.exists());
        boolean deleted = file.delete();
        System.out.println("file was deleted: " + deleted);
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
could not find one, would love to have one!

FREQUENCY : always



Comments
Code in question is this (in ResourceBundle.Control.newBundle()) ``` if (reloadFlag) { // Disable caches to get fresh data for // reloading. connection.setUseCaches(false); } return connection.getInputStream(); ``` I confirmed if I commented out "if (reloadFlag)", then "connection" is set to not use cache, and the jar file may be deleted after the call. However this defeats the purpose of "not reloading", and would get input stream everytime it is issued (not from cache). This would cost as a performance obstacle. This is inherently caused by the implementaiton in Windows JarURLConnection(JDK-4823678), thus I decided not to fix this in ResourceBundle calling site.
18-09-2019

The ResourceBundle code has been modified for the module system support that enforces the resource encapsulation. The code used to be calling ClassLoader.getResourceAsStream() if reload is false. The modified code always gets the input stream from URLConnection with "use cache" is true (default). On Windows this causes deletion of the jar file impossible (JDK-4823678).
17-09-2019

Reopening the issue. It seems to occur only on Windows. macOS seems to be fine.
16-09-2019

I tried the sample on JDK13, without the said library "commons-io", ie, directly put the MySQL jar file on some temporary directory. It turned out that I could not reproduce the problem. The jar file is deleted OK. Can the submitter make sure that it is not related with the commons-io library?
16-09-2019

Additional Information from submitter: FWIW, I think the bug is related to the fact that Java 8 ResourceBundle uses classLoader.getResourceAsStream which (in URLClassLoader) keeps a handle to the opened jar file in its closeables field, which then closes the jar file when classloader.close() is called. But the J11+ ResourceBundle does not use getResourceAsStream, so there's nothing that hangs on to the opened jar file.
21-02-2019

To reproduce the issue, run the attached test case. JDK 8u201 - Pass JDK 9 - Fail JDK 10 - Fail JDK 11 - Fail JDK 12-ea - Fail JDK 13-ea - Fail Output: C:\Users\XXXXX\AppData\Local\Temp\mysql-connector-java-5.1.36-bin.jar exists: true file was deleted: false
15-02-2019