0

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?

Mark Cramer
  • 2,614
  • 5
  • 33
  • 57
  • 1
    Whatever do you mean by exporting your program as a JRE? JRE generally stands for the program you use to run your java program, e. g. the `java` command is a part of JRE. I also fail to see what it has to do with `FileInputStream` which is just a plain old file reader that works the same as any kind of file-opening function. – Sergei Tachenov Aug 21 '16 at 07:55
  • 1
    "JRE" should be jar in question, and `ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("assets\\classes".replace("\\", "/")));` should be `ObjectInputStream inputStream = new ObjectInputStream(getClass().getResourceAsStream("/assets/classes"));` in code, to work in a jar file as intended. – guleryuz Aug 21 '16 at 11:53
  • It's a JAR, not a JRE. Sorry about that. So yes, I go Export -> Runnable JAR file -> etc. `ObjectInputStream inputStream = new ObjectInputStream(getClass().getResourceAsStream("/asset‌​s/classes"));` however is giving a NullPointException when I fun in Eclipse. – Mark Cramer Aug 21 '16 at 15:38

1 Answers1

1

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.

Sergei Tachenov
  • 24,345
  • 8
  • 57
  • 73
  • Getting closer. I've moved the file to MyProgram/src/resources/classes (I've unzipped the JAR and it's there) but `getClass().getResourceAsStream("src/resources/classes")` is returning null. (`getClass().getResourceAsStream("/src/resources/classes")` doesn't work either.) I don't have a class in "src" (they're all in "src/main") so how am I to not use `getClass()`? – Mark Cramer Aug 21 '16 at 17:23
  • This is frustrating. The problem with Eclipse is http://stackoverflow.com/a/2195501/852795. If I move the file to /src/main is works. BTW, if I move it /bin/main is also works. In any event, using `getRourseAsStream()` is the correct answer for the JAR, so I'll mark this 'accepted'. Thanks! – Mark Cramer Aug 21 '16 at 18:13
  • 1
    @Mark, see my edits about `getClass` and `.class`. You almost never want to use `getClass` for accessing resources because it can be overridden and just return a wrong thing. – Sergei Tachenov Aug 21 '16 at 18:52
  • Thanks. I've switched to MyClass.class. I've got the file to work, so now I'm working on doing the same thing with a folder of files. This is really nuts. – Mark Cramer Aug 21 '16 at 18:55
  • 1
    @Mark, as for `src/main`, see my edits about Maven and Gradle. I don't know whether you're using Maven/Gradle or not (maybe you should, that would solve a lot of problems like this!), but if you have `src/main/java`, then maybe you should have your resources in `src/main/resources`. Anyway, neither `src` or `src/main` should be present in the `getClass` call even if you use absolute paths, because they're *above* the CLASSPATH hierarchy, and absolute paths starts at the *same* level. – Sergei Tachenov Aug 21 '16 at 18:55
  • 1
    So I'd expect something like `src/main/java/my/package/MyClass.java`, `src/main/resources/my/package/assets/classes` and `MyClass.class.getResourceAsStream("assets/classes")` call. That would fit the usual pattern. At least that how Maven does things and that what most Java developers expect. – Sergei Tachenov Aug 21 '16 at 18:57
  • 1
    @Marc, see more edits about that (as it's considered a bad style to keep such things in comments). – Sergei Tachenov Aug 21 '16 at 19:02
  • For anyone who gets here, I got this working. I'm using Eclipse (no Maven or Gradle) so I moved all my files into src/main/resources (I created the folder) and then did `ObjectInputStream is = new ObjectInputStream(MyClass.class.getClassLoader().getResourceAsStream("classes"))`. I believe the `getClassLoader()` was the trick. Then `List listOfClasses = (List) is.readObject();` seems to work like a charm. Thanks for the help! – Mark Cramer Aug 23 '16 at 23:52