简体   繁体   中英

File Not Found Exception - data file in resource folder throwing exception when executing from jar

I have a Swing app that I'm trying to package up in a runnable JAR file. Part of its DAO functionality is reading and writing in CSV format to a .dat file inside src/main/resources/dictData.dat

My issue is that each time I try to run the jar, I get

java.io.FileNotFoundException: file:/Users/jason/projects/test-dict/target/
    dictionary-jar-with-dependencies.jar!/dictData.dat 
    (No such file or directory)

from the command line. This is from a jar built via mvn package and the maven-assembly-plugin specifications

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.4.1</version>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifest>
                        <mainClass>com.test.dictionary.init.AppInit</mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

I have a FileIO class implementation that handles the reading and writing to the file.

public class FileIO implements IO{

    private static final String DICTIONARYFILE = "dictData.dat";
    private File dataFile;
    private Writer dataWriter;
    private Reader dataReader;

    @Override
    public Map<String, Word> loadDataFile(){
        ClassLoader classLoader = getClass().getClassLoader();
        Map<String, Word> dictMap = new HashMap<>();

        try {
            //Open file connection and read stream
            dataFile = new File(classLoader.getResource(DICTIONARYFILE).getFile());
            dataReader = new FileReader(dataFile);

            //CSV parsing code here
        } catch (NullPointerException | IOException | NumberFormatException e){
            e.printStackTrace();
        }
    }
}

I only get this error when executing

java -jar dictionary-jar-with-dependencies.jar

inside /Users/jason/projects/test-dict/target/ . If I run via the IDE, I get no errors.

Thing is, I can see dictData.dat at the end of the jar via vim dictionary-jar-with-dependencies.jar :

...
... 
com/test/dictionary/utils/FileIO.class
com/test/dictionary/utils/HttpUtils.class
com/test/dictionary/utils/IO.class
dictData.dat
META-INF/maven/com.test/
META-INF/maven/com.test/dictionary/
META-INF/maven/com.test/dictionary/pom.xml
META-INF/maven/com.test/dictionary/pom.properties

So, short of moving the data file into a test-dict/com/text/dictionary/data location, how can I resolve this issue?

You can't convert the URL returned by classLoader.getResource(DICTIONARYFILE) to a file name because an application resource usually isn't an actual file. Even if you could, URL.getFile() is the wrong way to do it. Use this instead:

URL dataFile = classLoader.getResource(DICTIONARYFILE);
dataReader = new InputStreamReader(dataFile.openStream());

Detailed explanation:

When your program is running from a .jar file (which nearly all programs do, except in some development environments), the Class.getResource and ClassLoader.getResource methods return a URL which is a jar URL . A jar URL is a Java-specific URL scheme with this format:

jar: url-of-jarfile ! path-of-jar-entry

In your case, the .jar file was located at /Users/jason/projects/test-dict/target/dictionary-jar-with-dependencies.jar . In URL form, that is file:/Users/jason/projects/test-dict/target/dictionary-jar-with-dependencies.jar .

The entry within that jar file which you requested /dictData.dat . So the URL returned by your getResource call was:

jar:file:/Users/jason/projects/test-dict/target/dictionary-jar-with-dependencies.jar!/dictData.dat

URL.getFile() does not do what you think it does. In particular, URL.getFile() does not convert a URL into a filename. It merely returns the path portion of a URL. The path portion is not a filename; it is just whatever part of the URL comes after the scheme/authority/host/port, up to first question mark ('?') or hash ('#').

In the case of a jar URL, the path portion is everything that comes after the 4 characters jar: . So you effectively called new File("file:/Users/jason/projects/test-dict/target/dictionary-jar-with-dependencies.jar!/dictData.dat") .

To read from a URL, you should not try to convert it to a file and you should not assume the URL is a file: URL. As you have seen, it often is not. Read from the InputStream returned by the URL's openStream() method instead.

If you're wondering why a method named "getFile" does not actually return a file, the reason is that java.net.URL is a very old class. It was present in Java 1.0, way back in the mid-90s. At that time, most URLs did in fact point to physical files, especially ftp: URLs, which were still far more common than http: URLs. The java.net.URI class is newer and uses more accurate terminology. (Every URL is also a URI.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM