Mark Cramer Mark Cramer - 3 months ago 23
Java Question

Exported JAR unable to read local file

My Java application has a local file called "classes" (a simple

List<String>
) in a top-level folder called "assets". When I run the code below from Eclipse, the file is read no problem.

try { // read in classes from local file
ObjectInputStream inputStream =
new ObjectInputStream(new FileInputStream("assets\\classes".replace("\\", "/")));
listOfClasses = (List<String>) inputStream.readObject();
System.out.println("*** Reading local copy of classes file ***");
inputStream.close();
} catch (FileNotFoundException e) { e.printStackTrace();
} catch (ClassNotFoundException e) { e.printStackTrace();
} catch (IOException e) { e.printStackTrace(); }


When I export my application as a JAR, and run it on a different computer, I get a
java.io.FileNotFoundException
on the 3rd line. I added the "assets" folder to the build path, so it should be in the JAR.

Anyone know what's wrong here?

Answer

FileOutputStream just opens a local file. It should be absolute (/home/user/... or c:\temp\...) or relative to the current directory. On Windows, for example, that's the directory containing the JAR, by default.

If you meant to package your classes file into the JAR, then you should use getResourceAsStream, as @guleryuz mentioned. The path should be either absolute starting from the CLASSPATH root (that is, the root of the JAR file), like "/asset‌​s/classes", or (better) relative to the package of the calling class.

Speaking of classes, I'd strongly advise against using getClass() because it's a polymorphic method. There are two ways to get the class: by calling getClass() on an object or by accessing the static class “field”. If you have an instance of MyClass called myObject, then myObject.getClass() and MyClass.class are essentially the same thing. But! If your class is overridden by a class from another package, say, TheirClass, then, for an instance of TheirClass, getClass() will return TheirClass.class, even if you are calling getClass() from your code. So, unless your class is declared final, it is always a bad idea to use getClass().getResource... because you never know when someone overrides your class. And you're not supposed to know or even care!

Given all that, say, you have a class called MyClass in a package called my.package. The JAR could have the following structure:

my/
    package/
        MyClass.class
        assets/
            classes

Then, calling MyClass.class.getResourceAsStream("assets/classes") should do the trick. Alternatively, that could be MyClass.class.getResourceAsStream("/my/package/assets/classes"). Note that the absolute path does not start from the project directory, where you may have src or src/main or whatever your build system uses. It is relative to the CLASSPATH root (that's right, the absolute path is relative to the CLASSPATH root, even if that sounds confusing). That is, it starts from the directory where your packages are.

The last, but not least, is that if you use Maven-like build system (Maven or Gradle, for example), then Java sources go to src/main/java, but resource files like your file go to src/main/resources. They end up in exactly the same place in your JAR file. That is, src/main/java/package/MyClass.java is compiled into package/MyClass.class, and src/main/resources/package/assets/classes go into package/assets/classes. But in order for the build system to figure out what to do with your files, you must place them into the right directories: *.java go to java, resources go to resources.

So, assuming Maven-like conventions, the project structure should be

src/
    main/
        java/
            my/
                package/
                    MyClass.java
src/
    main/
        resources/
            my/
                package/
                    assets/
                        classes

Given such a structure, you'll get a JAR file with the structure like above. Looks a bit crazy, but very useful once you get a hang of it.

If you're not using a Maven-like build system, then I have no idea how to configure Eclipse. Different IDEs may have their own conventions, and I've always used Maven with Eclipse. Some IDEs like to put Java sources and resources into the same directory tree, for example.