230

I wonder what the difference is between Class.getResource() and ClassLoader.getResource()?

edit: I especially want to know if any caching is involved on file/directory level. As in "are directory listings cached in the Class version?"

AFAIK the following should essentially do the same, but they are not:

getClass().getResource() 
getClass().getClassLoader().getResource()

I discovered this when fiddling with some report generation code that creates a new file in WEB-INF/classes/ from an existing file in that directory. When using the method from Class, I could find files that were there at deployment using getClass().getResource(), but when trying to fetch the newly created file, I recieved a null object. Browsing the directory clearly shows that the new file is there. The filenames were prepended with a forward slash as in "/myFile.txt".

The ClassLoader version of getResource() on the other hand did find the generated file. From this experience it seems that there is some kind of caching of the directory listing going on. Am I right, and if so, where is this documented?

From the API docs on Class.getResource()

Finds a resource with a given name. The rules for searching resources associated with a given class are implemented by the defining class loader of the class. This method delegates to this object's class loader. If this object was loaded by the bootstrap class loader, the method delegates to ClassLoader.getSystemResource(java.lang.String).

To me, this reads "Class.getResource is really calling its own classloader's getResource()". Which would be the same as doing getClass().getClassLoader().getResource(). But it is obviously not. Could someone please provide me with some illumination into this matter?

oligofren
  • 20,744
  • 16
  • 93
  • 180

9 Answers9

301

Class.getResource can take a "relative" resource name, which is treated relative to the class's package. Alternatively you can specify an "absolute" resource name by using a leading slash. Classloader resource paths are always deemed to be absolute.

So the following are basically equivalent:

foo.bar.Baz.class.getResource("xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("foo/bar/xyz.txt");

And so are these (but they're different from the above):

foo.bar.Baz.class.getResource("/data/xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("data/xyz.txt");
Derek Mahar
  • 27,608
  • 43
  • 124
  • 174
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Nice answer with clear examples. Although the post actually was meant to get answers to two questions, I see now that the second questions is sort of hidden. Quite unsure of how/whether I should update the post to reflect this, but what I would like to know second is this (next comment): – oligofren Jul 07 '11 at 16:04
  • 3
    Is there some kind of caching going on in the Class.getResource() version? What caused me to believe this is the generation of some jasper reports: We use getClass().getResource("/aDocument.jrxml") to fetch the jasper xml file. A binary jasper file is then produced in the *same* directory. getClass().getResource("/aDocument.jasper") is not able to find it, although it can clearly find documents at the same level (the input file). This is where ClassLoader.getResource() proved helpful, as it seems it does not use caching of the directory listing. But I cannot find documentation on this. – oligofren Jul 07 '11 at 16:08
  • 3
    @oligofren: Hmm... I wouldn't *expect* Class.getResource() to do any caching there... – Jon Skeet Jul 07 '11 at 16:18
  • 1
    @JonSkeet why `this.getClass().getClassLoader().getResource("/");` returning null? It should not be same as `this.getClass().getClassLoader().getResource(".");` – Asif Mushtaq Feb 16 '18 at 22:20
  • @UnKnown: I think you should probably ask a new question about that. – Jon Skeet Feb 17 '18 at 08:06
  • @JonSkeet the one with leading slash works flawlessly to fetch resources from test resources root! – Gaurav May 13 '19 at 12:27
29

The first call searches relative to the .class file while the latter searches relative to the classpath root.

To debug issues like that, I print the URL:

System.out.println( getClass().getResource(getClass().getSimpleName() + ".class") );
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • 4
    I think "classloader root" would be more accurate than "classpath root" - just to be picky. – Jon Skeet Jul 07 '11 at 10:14
  • 4
    Both can search "absolute paths" if the file name is prepended by "/" – oligofren Jul 07 '11 at 15:59
  • 3
    Interesting... I am hitting a case where getClass().getResource("/someAbsPath") returns a URL in the type /path/to/mylib.jar!/someAbsPath and getClass().getClassLoafer().getResource("/someAbsPath") return null... So "root of classloader" seems not to be a very well defined notion... – Pierre Henry Apr 16 '13 at 14:35
  • see http://stackoverflow.com/questions/13269556/strange-behavior-of-class-getresource-and-classloader-getresource-in-executa – Pierre Henry Apr 16 '13 at 14:36
  • 11
    @PierreHenry: `getClassLoader().getResource("/...")` always returns `null` - the classloader doesn't remove the leading `/` from the path, so the lookup always fails. Only `getClass().getResource()` handles a starting `/` as an absolute path relative to the classpath. – Aaron Digulla Apr 16 '13 at 14:43
19

Had to look it up in the specs:

Class's getResource() - documentation states the difference:

This method delegates the call to its class loader, after making these changes to the resource name: if the resource name starts with "/", it is unchanged; otherwise, the package name is prepended to the resource name after converting "." to "/". If this object was loaded by the bootstrap loader, the call is delegated to ClassLoader.getSystemResource.

mrk
  • 4,999
  • 3
  • 27
  • 42
Bernd Elkemann
  • 23,242
  • 4
  • 37
  • 66
  • 2
    Do you have any info on whether it caches the directory listing as well? This was the main difference between the two methods when when first looking up an input file, and then creating a file using that in the same directory. The Class version did not find it, the ClassLoader version did (both using "/file.txt"). – oligofren Jul 07 '11 at 16:22
12

All these answers around here, as well as the answers in this question, suggest that loading absolute URLs, like "/foo/bar.properties" treated the same by class.getResourceAsStream(String) and class.getClassLoader().getResourceAsStream(String). This is NOT the case, at least not in my Tomcat configuration/version (currently 7.0.40).

MyClass.class.getResourceAsStream("/foo/bar.properties"); // works!  
MyClass.class.getClassLoader().getResourceAsStream("/foo/bar.properties"); // does NOT work!

Sorry, I have absolutely no satisfying explanation, but I guess that tomcat does dirty tricks and his black magic with the classloaders and cause the difference. I always used class.getResourceAsStream(String) in the past and haven't had any problems.

PS: I also posted this over here

Community
  • 1
  • 1
Tim Büthe
  • 62,884
  • 17
  • 92
  • 129
  • 1
    This behaviour appears to be a bug in Tomcat that was fixed in version 8. I added a paragraph about that in my [answer about this question](http://stackoverflow.com/questions/676250/different-ways-of-loading-a-file-as-an-inputstream/676273#676273) – LordOfThePigs Jul 06 '14 at 00:31
7

To answer the question whether there is any caching going on.

I investigated this point further by running a stand-alone Java application that continuously loaded a file from disk using the getResourceAsStream ClassLoader method. I was able to edit the file, and the changes were reflected immediately, i.e., the file was reloaded from disk without caching.

However: I'm working on a project with several maven modules and web projects that have dependencies on each other. I'm using IntelliJ as my IDE to compile and run the web projects.

I noticed that the above seemed to no longer hold true, the reason being that the file that I was being loaded is now baked into a jar and deployed to the depending web project. I only noticed this after trying to change the file in my target folder, to no avail. This made it seem as though there was caching going on.

mchlstckl
  • 3,390
  • 2
  • 21
  • 21
  • I too used Maven and IntelliJ, so this is the answer witn an environment that most closely matches mine and has a reasonable explanation for question #2. – oligofren Jan 10 '14 at 09:55
3

Since Java 9 there is a pitfall with ClassLoader#getResource when running on the module path. Because of this, I would never use ClassLoader#getResource in new code.

If your code is in a named module, and you use ClassLoader#getResource, your code can fail to retrieve a resource even if the resource is in the same module. This is very surprising behavior.

I experienced this myself and was very surprised as to this difference between Class#getResource and ClassLoader#getResource. However, it is entirely specified behavior according to the javadoc:

Additionally, and except for the special case where the resource has a name ending with ".class", this method will only find resources in packages of named modules when the package is opened unconditionally (even if the caller of this method is in the same module as the resource).

Javadoc (emphasis mine)

A248
  • 690
  • 7
  • 17
  • related: [What is an open module in Java 9 and how do I use it?](https://stackoverflow.com/questions/46482364/what-is-an-open-module-in-java-9-and-how-do-i-use-it) – jaco0646 Apr 08 '21 at 14:34
2

Class.getResources would retrieve the resource by the classloader which load the object. While ClassLoader.getResource would retrieve the resource using the classloader specified.

hackjutsu
  • 8,336
  • 13
  • 47
  • 87
Jackie
  • 25,199
  • 6
  • 33
  • 24
0

I tried reading from input1.txt which was inside one of my packages together with the class which was trying to read it.

The following works:

String fileName = FileTransferClient.class.getResource("input1.txt").getPath();

System.out.println(fileName);

BufferedReader bufferedTextIn = new BufferedReader(new FileReader(fileName));

The most important part was to call getPath() if you want the correct path name in String format. DO NOT USE toString() because it will add some extra formatting text which will TOTALLY MESS UP the fileName (you can try it and see the print out).

Spent 2 hours debugging this... :(

Kevin Lee
  • 2,307
  • 26
  • 31
  • What about [Class.getResourceAsStream()](https://docs.oracle.com/javase/9/docs/api/java/lang/Class.html#getResourceAsStream-java.lang.String-)? – Ilya Serbis Jan 15 '18 at 15:34
  • Resources are not files. They might not be unpacked from the JAR or WAR file, and if not a `FileReader` or `FileInputStream` cannot be used to access them. Answer is not correct. – user207421 Jul 03 '18 at 02:30
0

Another more efficient way to do is just use @Value

@Value("classpath:sss.json")
private Resource resource;

and after that you can just get the file this way

File file = resource.getFile();

thelastworm
  • 544
  • 4
  • 14
  • 1
    This is really not relevant to the discussion about classloaders. No one cares how to load a file here, but what makes the difference. – oligofren Jan 06 '21 at 22:01