3

My program read configuration data by reading xml file fro current directory:

File fXmlFile = new File("configFile.xml");

It works fine in NetBeans IDE. I have build project and got jar file. It runs fine if I double click on it in Windows 10. In case I open file by using right click on jar and Open with -> Java program can't find configuration file. In this case I got exception:

java.io.FileNotFoundException: C:\Windows\System32\configFile.xml (The system cannot find the file specified)

Why it looks just to system path and not in current directory? How to ask program to load file in current directory when running in Open with -> Java case?

Jar's manifest file:

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.10.4
Created-By: 12.0.1+12 (Oracle Corporation)
Class-Path: lib/log4j-api-2.11.2.jar lib/log4j-core-2.11.2.jar lib/met
 ouia.jar lib/swt.jar
X-COMMENT: Main-Class will be added automatically by build
Main-Class: com.aaa.myprog.runMe 
vico
  • 17,051
  • 45
  • 159
  • 315

3 Answers3

2

The current directory is, as Victor already pointed out, dependent on the command that is used to launch the JVM and therefore dynamic at runtime. You instead need a locator that is dependent on the location on the jar file itself, meaning it is dynamic at compile time but static at runtime.

There are different approaches here, so let me shortly introduce two:

Use a launcher script
This way you simply take control of the command line yourself, but you have to do it for every operating system where you plan to use your program. On Windows it could look like this:

app.bat:

cd %~dp0
java -jar app.jar

More information on the first line here.

Use System ClassLoader
This works, because the System ClassLoader's sources are dynamic at compile time but static at runtime, so exactly what you need. However, it comes with the downside that you cannot write to the configuration file, as you only get an InputStream.

app.jar

try (InputStream fXml = ClassLoader.getSystemClassLoader().getResourceAsStream("configFile.xml")) {
    ...
}

And a full MCVE.

ConfigFile.java:

public class ConfigFile {
    public static void main(String[] args) {
        try (final BufferedReader configFile = new BufferedReader(
            new InputStreamReader(ClassLoader.getSystemClassLoader()
                .getResourceAsStream("configFile.txt")))) {
            System.out.println(configFile.readLine());
        } catch (final IOException exc) {
            exc.printStackTrace();
        }
    }
}

ConfigFile.txt

Hello World

MANIFEST.MF

Manifest-Version: 1.0
Class-Path: .
Main-Class: prv.izruo.test.ConfigFile

command line

P:\workspace\ConfigFile>dir deploy
...
02.05.2019  20:43             1.434 configFile.jar
02.05.2019  20:43                11 configFile.txt

P:\workspace\ConfigFile>java -jar deploy\configFile.jar
Hello World
Izruo
  • 2,246
  • 1
  • 11
  • 23
  • I prefer not use any startup scrip I choose second approach. But I got null in fXml – vico May 01 '19 at 16:12
  • In case I put `configFile.xml` inside of jar file everything goes fine. How to ask system get resources not from jar, but from same directory where jar file is located? – vico May 01 '19 at 17:00
  • Honestly, I have no idea. Today I re-checked my project at work and composed an MCVE at home. Both are perfectly fine. – Izruo May 02 '19 at 18:46
  • @vico I think I found it studying the Manifest of my MCVE: You must add `.` to your classpath (or whatever folder you wish to save your config file to). – Izruo May 02 '19 at 19:00
2

The best way for you to read config.xml and other assets your app may need is putting them in src/main/resources and then referencing them as files in your classpath, like this:

Shell

mv configFile.xml /users/vico/my_program/src/main/resources

Java Code

// ...

public static File getResourceAsFile(String resourcePath) {
    try {
        InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
        if (in == null) {
            return null;
        }

        File tempFile = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
        tempFile.deleteOnExit();

        try (FileOutputStream out = new FileOutputStream(tempFile)) {
            //copy stream
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }
        return tempFile;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

File fXmlFile = getResourceAsFile("configFile.xml");

// ...

(this code was borrowed from this stackoverflow answer)

Then you can move your jar wherever you want, even ship it to your users and stop worrying about where the config files lie.

Pablo Fernandez
  • 103,170
  • 56
  • 192
  • 232
-1

As I see the problem arises because you need to know how relative paths are resolved at execution time, and the "current directory" takes place on this equation, so allow me to explain a little bit.

As far I know the "current directory" is also knows as working directory and it is set when you launch a Java application and it assumes the value of the directory from where you execute the Java command to launch the app.

So for example:

If you open a command terminal, and you place yourself in a directory "C:\pepe" and from within that directory you execute the command to launch your jar, e.g.: java -jar "c:\path\to\my\app.jar" then the working directory would be "c:\pepe\" and inside your program any reference to relative path would be completed using the "c:\pepe\" prefix.

And then the line File fXmlFile = new File("configFile.xml"); will instruct the jvm to look the file at "c:\pepe\configFile.xml", as the relative path configFile.xml will be resolved using the working directory (set previously at launch-time) as I explained above.

Having in mind how Java sets the working directory and how relative paths are resolved using the working directory as prefix, will help you solve any reference problem, from now onwards.

You may find this link useful: How to get the current working directory in Java? for investigating what is the working directory set by NetBeans when it launch (for us) the Java app.

halfer
  • 19,824
  • 17
  • 99
  • 186
Victor
  • 3,841
  • 2
  • 37
  • 63
  • “If you execute `c:\pepe\java -jar "c:\path\to\my\app.jar"` the working directory would be `"c:\pepe\"`” —That is not correct. You can make any directory the current working directory using `cd`, then execute that same command. The working directory is not defined by the location of the executing program, it’s a property of the individual process or parent command shell. – VGR May 01 '19 at 15:04
  • thanks for pointing that out, as far i know without any aditional configuration your working directory defaults to the directory from where you execute the command. If there is any aditional way to set it, please, enlight us, feel free to edit my post in order to enumarate other ways to set the working directory. – Victor May 01 '19 at 15:09
  • `cd C:\pepe` is how you change the working directory. – VGR May 01 '19 at 15:10
  • ahhh understand what you are saying, allow me to fix my post – Victor May 01 '19 at 15:11
  • @VGR thanks for suggestion, i have edit the post; have you any other observation? again: feel free to edit my post – Victor May 01 '19 at 15:15