2

I just don't understand why using the ClassLoader causes these two cases to act differently. Can someone explain how/why the ClassLoader changes the search such that it requires a full path into the jar?

package com.example;
import java.io.InputStream;

public class SomeClass {
  private static InputStream stm;
  static {
    for (final String s : new String[] { "file.png", "com/example/file.png", "/com/example/file.png" }) {
      // case 1 - w/o classLoader
      stm = SomeClass.class.getResourceAsStream(s);
      System.out.println("w/o          : " + (stm == null ? "FAILED to load" : "loaded") + " " + s);

      // case 2 - w/ classLoader
      stm = SomeClass.class.getClassLoader().getResourceAsStream(s);
      System.out.println("w/classloader: " + (stm == null ? "FAILED to load" : "loaded") + " " + s);
    }
  }

  public static void main(final String args[]) {}
}

Produces:

w/o          : loaded file.png
w/classloader: FAILED to load file.png
w/o          : FAILED to load com/example/file.png
w/classloader: loaded com/example/file.png
w/o          : loaded /com/example/file.png
w/classloader: FAILED to load /com/example/file.png
5
  • The behavior you’re seeing is intentional. It is described in explicit detail in the documentation for Class.getResource and ClassLoader.getResource. Commented May 15, 2016 at 3:31
  • @VGR Class.getResources() says that w/o a leading /, it uses modified_package_name/name, so file.png becomes com/example/file.png and com/example/file.png becomes com/example/com/example/file.png, so 1st & 2nd string results make sense, and /com/example/file.png becomes com/example/file.png, which makes sense. But classLoader just says "This method will first search the parent class loader for the resource", which should match the w/o output unless I misunderstand that sentence. Why doesn't it? Commented May 15, 2016 at 3:43
  • ClassLoader.getResource says: “The name of a resource is a '/'-separated path name that identifies the resource.” There’s no leading /, just a series of path components separated by /. Commented May 15, 2016 at 3:56
  • @VGR that ClassLoader.getResource description of a resource doesn't say the path starts at the top of the package tree. If I put the file in an images subfolder beneath the class, images/file.png would meet that requirement as written, but it would still require me to use the path com/example/images/file.png to actually work. Commented May 15, 2016 at 4:25
  • A ClassLoader doesn’t have any one class to load things relative to. There is no starting point other than the root of each classpath entry. If a ClassLoader were to load, say, 50 classes from several different packages, which of those classes would you expect a relative path to be resolved against? Commented May 15, 2016 at 4:43

1 Answer 1

4

Because the path is evaluated differently depending on which getResourceAsStream() method you call.
If you call Class.getResourceAsStream() on the class com.example.SomeClass, then the path is evaluated relatively to SomeClass if it does not start with a /, so file.png becomes /com/example/file.png and com/example/file.png becomes /com/example/com/example/file.png.
If you call ClassLoader.getResourceAsStream() on its ClassLoader, then the path is implicitly absolute and must not start with a /, so file.png becomes /file.png and com/example/file.png becomes /com/example/file.png.
If you use an absolute path like /com/example/file.png, you have to use the Class method.

Unfortunately this implicit absoluteness of ClassLoader.getResourceAsStream() is only documented implicitly in the documentation of Class.getResourceAsStream(), which states that it strips the leading slash before it delegates to its ClassLoaders method.

Sign up to request clarification or add additional context in comments.

4 Comments

thank you, that kinda makes sense. But then I tried using /com/example/file.png and it worked the same as using just file.png which doesn't fit your otherwise good explanation, i.e. the absolute path worked without the classLoader and failed when using the classLoader.
Not correct. An argument starting with / is never valid for ClassLoader.getResource. All arguments to that method are implicitly absolute paths.
Thanks VGR, I forgot about that, especially as the doc on this is crappy. @user1663987 I updated my answer to give the correct information.
Given that each class has a classLoader, and each classLoader has a parent it delegates to initially, the absolute path aspect of it is counterintuitive since it initially seems it should be class relative, and the poorly written implicit documentation doesn't help in understanding this issue. Thanks for writing it up.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.