I would like to have a config file outside my jar. The config will be read and the program should behave accordingly. I tried to use:
this.getClass().getResource( file_name );
But i ran into an issue with the pom dependencies. Below example illustrates this issue.
The src file structure is:
main
├── java
│ └── com
│ └── company
│ └── App.java
└── resources
└── external
└── input
└── external_file.txt
the target file structure is:
target/
├── classes
│ └── com
│ └── company
│ └── App.class
├── resources
│ └── input
│ └── external_file.txt
│
└── test-file.jar
the complete code:
package com.company;
import java.io.*;
import java.net.URL;
/**
* Hello world!
*
*/
public class App
{
public void run() {
//the module options are extended to add the resources folder to the classpath in intelliJ
String file_name = File.separator + "input" + File.separator + "external_file.txt";
System.out.println( "file name: " + file_name );
URL propertiesFileUrl = this.getClass().getResource( file_name );
String path = propertiesFileUrl.getPath();
System.out.println( "path: " + path );
File test_file = new File( path );
try{
FileReader test_fileReader = new FileReader( test_file );
BufferedReader test_bufferedReader = new BufferedReader( test_fileReader );
String test_line = test_bufferedReader.readLine();
test_fileReader.close();
System.out.println( "First line: " + test_line );
} catch(FileNotFoundException e){
e.printStackTrace();
} catch(IOException e){
e.printStackTrace();
}
}
public static void main( String[] args )
{
new App().run();
}
}
this code is working fine if I use the following pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.company</groupId>
<artifactId>test-file</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>test-file</name>
<url>http://maven.apache.org</url>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifest>
<mainClass>com.company.App</mainClass>
</manifest>
<manifestEntries>
<Class-Path>resources/</Class-Path>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-resources</id>
<!-- here the phase you need -->
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/resources</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources/external</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<excludes>
<exclude>external/</exclude>
</excludes>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
I can execute the code with IntelliJ as I have added the target/resources folder as a dependency in my module settings. I can also execute the jar from the command line and the output is as expected:
file name: /input/external_file.txt
path: /Users/amin/Work/testjar/resources/input/external_file.txt
First line: test text!!!
But as soon as I add the following dependency into my pom.xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
<scope>compile</scope>
</dependency>
, reimport and rebuild everything, I can still execute the program successfully from IntelliJ, but the jar executed form command line will output:
file name: /input/external_file.txt
Exception in thread "main" java.lang.NullPointerException
at com.company.App.run(App.java:19)
at com.company.App.main(App.java:39)
In my little universe of understanding, this doesn't make sense at all note: mysql is not the only package, that will change the result to not working.
A resource is an entry inside a .jar file. You cannot refer to it as a file, because it’s not an individual file. Resources should be read with getResource or getResourceAsStream, whereas files should never be read with those methods, because getResource is not for reading files, it’s for reading resources.
Note that the argument to getResource and getResourceAsStream is not a filename. It is a relative URL indicating an embedded resource. This means it must not use File.separator—a URL always uses forward slashes (/
) on all platforms.
You haven’t shown whether your test-file.jar actually contains external_file.txt. Your title suggests external_file.txt is not, in fact, inside the .jar. If that’s the case, you should not be using getResource as all, but should instead use the Files class:
try (BufferedReader reader = Files.newBufferedReader(
Paths.get(System.getProperty("user.home"),
"projects", "app", "target", "resources",
"external", "input", "external_file.txt")) {
String test_line = reader.readLine();
System.out.println( "First line: " + test_line );
}
(Note that the try-with-resources statement automatically closes the Reader.)
However… I am not sure you really want that file to be external. It is long standing convention for files placed in a project’s resources
directory to be packaged inside the .jar file at build time. Have you checked the contents of test-file.jar to verify whether input/external_file.txt is in it?
If it is, you should read it with getResourceAsStream:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
App.class.getResourceAsStream("/input/external_file.txt"))) {
String test_line = reader.readLine();
System.out.println( "First line: " + test_line );
}
Side note: The getPath() method of a URL does not convert a URL to a valid file name. It only returns the path portion of the URL, which is by no means guaranteed to be a valid file name. For instance: the file C:\Users\John Doe
would be represented as the URL file:/C:/Users/John%20Doe
, because space characters are not allowed in URLs; the getPath() method would return "/C:/Users/John%20Doe"
.