Search code examples
javaclassloaderloading

Keystore not loading JKS file from within jar


After hours of fighting with this, I'm starting to get really frustrated.

I have a JKS server file that I want to load from within the jar. Using FileInputStream works a treat as long as the file is outside the Jar, but as soon as I try to change that to getResourceAsStream it will just not take it, saying (No such file or directory).

This is how the project is distributed

ProjectFolder
-src
--package.name
---JKS File
---Class calling the resource retrieval and loading into the keystore

I think I have tried almost every combination of this.getClass().getClassLoader().getResourceAsStream("jksFile.jks") I can think of.

I apologise in advance for not being more specific with the code, but I have literally lost count of what I have tried and what I havent.

HELP!

Thanks in advance


Solution

  • There are two main ways of accessing resources inside the classpath, through the Class object, and through the Classloader, by getting getResourceAsStream.

    Ultimately, the precise rules for implementing the lookup of the resource is class loader dependant. But most of it is clearly specified. The resource is looked up by name.

    The name of a resource is a '/'-separated path name that identifies the resource.

    And, when exploring the classpath, the search order is also clear

    This method will first search the parent class loader for the resource; if the parent is null the path of the class loader built-in to the virtual machine is searched. That failing, this method will invoke {@link #findResource(String)} to find the resource.

    The findResource is the part that is implementation dependent. However, most Java classloaders are URLClassLoaders of some sort, and their implementation know how to explore the classpath entries (on the file system or inside JAR files - even remote ones) to resolve names as relative or absolute paths inside said-entries.

    So, in a nutshell : you use getResourceByName to explore your classpath looking for a file by relative or absolute path in the class path.

    The difference between Class#getResourceAsStream and ClassLoader#getResourceAsStream is that the Class version will interpret relative path as relative to the Class's package, while the ClassLoader version will start at the root of the classpath.

    So... Given the following project structure :

    src
      name
        gpi
          file.txt
          Test.java  
    

    This Test.java works :

    public static void main(String[] args) throws IOException {
                // relative path from this class
        InputStream is = Test.class.getResourceAsStream("file.txt");
        byte[] content = new byte[4096];
        int length = is.read(content);
        System.out.println(new String(content, 0, length));
    
                // relative path from the root of the classpath
        is = Test.class.getClassLoader().getResourceAsStream("name/gpi/file.txt");
        content = new byte[4096];
        length = is.read(content);
        System.out.println(new String(content, 0, length));
    }
    

    Be careful in details in the documentation thought, as

    // This works : on the class object, absolute path are treated as "root of the classpath"
    InputStream is = Test.class.getResourceAsStream("/name/gpi/file.txt");
    
    //This does not work, because this looks actually at the root of the file system
    InputStream is = Test.class.getClassLoader().getResourceAsStream("/name/gpi/file.txt");
    

    If you try the combinations listed here as working, and they actually don't, then either you have a "funky" classloader (which should definitely not happen on a simple project), either your compiler / packager settings (maven or eclipse or whatever) are setup in a way that discards your JKS file from the compiled/packaged result (JAR).

    In maven projects in general, Java classes belong to src/main/java directory, whereas properties, configuration, and JKS files usually belong to the src/main/resources directory. Depending on you exact maven setting, putting a JKS file inside the java directory may result in it being discarded from the actual, runnable classpath. So you should check that too.