Save to external text-file from runnable jar

I have trouble getting my runnable jar file to write to an external text file. My intent is to store data in a text file that is in the same folder as the runnable jar (NOT inside the jar); each line is one dataset, new datasets are appended at the end of the existing file. I have tried several different approaches that work perfectly when I run the code from Eclipse but as soon as it is exported to a runnable jar (also via Eclipse) it does no longer change the text file.

Here are the things I tried:

  1. OutputStreamWriter

     public void writeData(String dataset) {   try {           URL               url = getClass().getClassLoader().getResource("datasets.txt");     OutputStream       os = new FileOutputStream(url.getFile(), true);     OutputStreamWriter ow = new OutputStreamWriter(os);      String line = "\n" + dataset;      ow.append(line);     ow.close();     os.close();   } catch (IOException e) {     e.printStackTrace();   } } 
  2. BufferedWriter

    public void writeData(String dataset) {   try {      URL           fileUrl = getClass().getClassLoader().getResource("datasets.txt")      BufferedWriter writer = new BufferedWriter(new FileWriter(fileUrl.getPath(), true));       writer.newLine();       writer.write(dataset);      writer.close();     } catch (IOException e) {      e.printStackTrace();    }  } 
  3. FileWriter

    public void writeData(String dataset) {      try {     URL              url = getClass().getClassLoader().getResource("datasets.txt");     FileOutputStream fos = new FileOutputStream(url.getFile(), true);     FileWriter       out = new FileWriter(getFilePath(), true);      out.append(dataset);     out.close();     fos.close();   }  catch (IOException e) {     e.printStackTrace();   } } 

Reading the file gave me some trouble too in the beginning but I got it to work with the InputStreamReader, adding the code just in case

private void readData() {     InputStream       is = getClass().getClassLoader().getResourceAsStream("datasets.txt");     InputStreamReader fr = new InputStreamReader(is);     BufferedReader    br = new BufferedReader(fr);      String line = null;     while((line = br.readLine())!= null) {               dataVec.add(dataset); ... 

I have never done anything like this before so I´d be grateful for any hints why these approaches dont´t work or any tips that might help, thanks in advance!

Add Comment
1 Answer(s)

All sorts of confusion in here:

  1. getResource and getResourceAsStream cannot be written to. You can’t convert these to files and pray that you can; resources are abstract concepts; specifically, the abstract concept that definitely makes zero promises about writability. Trivial case in point: Usually ‘at deployment’, java code is in jar files, and you cannot write to jar files in this manner, you generally don’t want to write into jar files, and even if you did, a proper secured setup tends to be such that the process cannot write to its own jar file, so this entire exercise is doomed to failure. You don’t use these. at all.

  2. ‘write to the same place the jar is at’ is in general suspicious. Why? On windows circa 1990 that was extremely common, but it has never been a good place to write per-user files on any OS that wasn’t in the DOS/Windows/CPM line/MacOS line, and the 1980s have long since passed us by. It hasn’t been a particularly common model on Windows for well over a decade at this point either; instead, you’d write to for example the user’s Documents folder. The model itself is inferior: Writing to the same dir the executable code is at implies you could write to the code, and that in general is waiting to get your box p0wned, which is why OSes are abandoning this model in droves.

  3. If you MUST write to the same folder the jar is in, well, that presumes your code by necessity always runs as a jar which is a bizarre assumption to encode in your source files. What if it’s not a jar, such as when you’re debugging in eclipse? Next to the classpath entry hosting your class files, so, for your average eclipse project ~/workspace/projname, as that is the dir containing ~/workspace/projname/bin which is where the compiled class files are? Okay, you CAN do that. But you don’t want to do that (see points 1 and 2). What if the classes are sourced from the network, generated on the fly, or otherwise from a classloader that doesn’t have the notion of ‘the source classpath entry where this came from is <this jar or this directory>?’ – Just pray your code is never deployed in such circumstances? I’d at the very least offer a -D based alternate option (which you can read with System.getProperty, so, in your java code String overrideLocation = System.getProperty("islandersproject.homedir"); and to start your app: java -Dislandersproject.homedir=/Users/islander/documents -jar islandersproject.jar.

Okay, so how do I do it?

Well, I said you could. But it is not easy.

For any given class, you can obtain the location where it was loaded from using this construct:

ClassName.class.getResource("ClassName.class").toString()

As long as the class in question isn’t an inner class, that works. For example:

System.out.println(String.class.getResource("String.class").toString()) – give that a try.

The string you get could be anything. IF it is a file (for example, if the class is /Users/islander/workspace/islandproject/bin/com/foo/island/islandproject/Island.class, you get that string, with file:// tacked to the front. If it’s jar, you get something like: jar:file://Users/islander/blabla/islanderproject.jar!com/foo/island/islandproject/Island.class – if it’s a jmod, it starts with jmod: (run this on String on JDK11 or 14 to see it in action), if it’s the network or generated on the fly it’ll be even more exotic.

If you really are quite sure you want to do this, parse the string.

Note that ‘.’ (the current working directory) is by no means guaranteed to be right; if you run on the command line on windows, for example:

C: CD \foobar java -jar d:\workspace\dist\islander.jar 

Then . would be the completely unrelated c:\foobar, as that was the working dir you started in. Therefore unlikely to be right, even if you luck into that working out when testing (as eclipse and such will set it to a sane place when you start the app from within eclipse).

Answered on July 16, 2020.
Add Comment

Your Answer

By posting your answer, you agree to the privacy policy and terms of service.