Search code examples
javamavenpropertiesclasspathmaven-assembly-plugin

Loading an external properties file in sibling directory


Following scenario

./
 config/
    application.properties
 lib/
    properties-loader-0.0.1-SNAPSHOT.jar
 acme.sh

properties-loader-0.0.1-SNAPSHOT.jar is an executable jar with a manifest file. There is one package com.acme.lab within the jar, containing only the class with the fully qualified name com.acme.lab.PropertiesLoader.

The script acme.sh executes the following command:

java -cp etc/application.properties:./lib/properties-loader-0.0.1-SNAPSHOT.jar com.acme.lab.PropertiesLoader

I'm trying to access the properties file from the PropertiesLoader class. I read the article Smartly load your properties but still have problems to access the properties file

System.out.println(this.getClass().getResourceAsStream("../etc/application.properties"));
System.out.println(this.getClass().getResourceAsStream("etc/application.properties"));
System.out.println(this.getClass().getResourceAsStream("/etc/application.properties"));
System.out.println(this.getClass().getResourceAsStream("application.properties"));

System.out.println(this.getClass().getClassLoader().getResourceAsStream("../etc/application.properties"));
System.out.println(this.getClass().getClassLoader().getResourceAsStream("etc/application.properties"));
System.out.println(this.getClass().getClassLoader().getResourceAsStream("/etc/application.properties"));
System.out.println(this.getClass().getClassLoader().getResourceAsStream("application.properties"));

try {
    System.out.println(ResourceBundle.getBundle("etc.application"));
    System.out.println(ResourceBundle.getBundle("application"));
} catch(MissingResourceException e) {
    // do nothing
}

All these calls fail to load the file.

I just know that the error has something to do with the classpath, but can't seem to find it. I created a sample maven project on github that recreates the problem.


Solution

  • Resources are interpreted from their classpath roots. Which in your case when you run your program like this:

    java -cp etc/application.properties:./lib/properties-loader-0.0.1-SNAPSHOT.jar
    

    The roots are

    • ./etc/application.properties
    • ./lib/properties-loader-0.0.1-SNAPSHOT.jar

    Neither of which contains your application.properties file (as a sub-resource). If you modified your command like this:

    java -cp etc:./lib/properties-loader-0.0.1-SNAPSHOT.jar
    

    Then you could read your properties file in your program as:

    Thread.currentThread().getContextClassLoader().getResourceAsStream("application.properties");
    

    As a side note, its always best to use fully qualified paths in your classpath setting.


    * EDIT *

    This is a working example that should illustrate resource loading:

    mkdir props; cd props
    mkdir etc; touch etc/application.properties
    mkdir test; vi test/PropLoader.java
    

    Paste this content into the editor then save:

    package test;
    
    import java.io.InputStream;
    
    public class PropLoader {
       public static void main(String[] args) {
          try {
             final String path;
             if(args.length == 1) path = args[0].trim();
             else path = "etc/application.properties";
    
             final InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
             if(is == null) throw new RuntimeException("Failed to load " + path + " as a resource");
             else System.out.printf("Loaded resource from path: %s\n", path);
          } catch(Exception e) {
             e.printStackTrace();
          }
       }
    }
    

    And to test:

    javac test/PropLoader.java
    java -cp . test.PropLoader
    

    Output is Loaded resource from path: etc/application.properties.