getResourceAsStream() vs FileInputStream
Asked Answered
J

6

186

I was trying to load a file in a webapp, and I was getting a FileNotFound exception when I used FileInputStream. However, using the same path, I was able to load the file when I did getResourceAsStream(). What is the difference between the two methods, and why does one work while the other doesn't?

Jerk answered 22/2, 2010 at 0:52 Comment(0)
N
263

The java.io.File and consorts acts on the local disk file system. The root cause of your problem is that relative paths in java.io are dependent on the current working directory. I.e. the directory from which the JVM (in your case: the webserver's one) is started. This may for example be C:\Tomcat\bin or something entirely different, but thus not C:\Tomcat\webapps\contextname or whatever you'd expect it to be. In a normal Eclipse project, that would be C:\Eclipse\workspace\projectname. You can learn about the current working directory the following way:

System.out.println(new File(".").getAbsolutePath());

However, the working directory is in no way programmatically controllable. You should really prefer using absolute paths in the File API instead of relative paths. E.g. C:\full\path\to\file.ext.

You don't want to hardcode or guess the absolute path in Java (web)applications. That's only portability trouble (i.e. it runs in system X, but not in system Y). The normal practice is to place those kind of resources in the classpath, or to add its full path to the classpath (in an IDE like Eclipse that's the src folder and the "build path" respectively). This way you can grab them with help of the ClassLoader by ClassLoader#getResource() or ClassLoader#getResourceAsStream(). It is able to locate files relative to the "root" of the classpath, as you by coincidence figured out. In webapplications (or any other application which uses multiple classloaders) it's recommend to use the ClassLoader as returned by Thread.currentThread().getContextClassLoader() for this so you can look "outside" the webapp context as well.

Another alternative in webapps is the ServletContext#getResource() and its counterpart ServletContext#getResourceAsStream(). It is able to access files located in the public web folder of the webapp project, including the /WEB-INF folder. The ServletContext is available in servlets by the inherited getServletContext() method, you can call it as-is.

See also:

Nadaba answered 22/2, 2010 at 1:59 Comment(1)
@khylo: related: stackoverflow.com/questions/7952090/…Nadaba
I
31

getResourceAsStream is the right way to do it for web apps (as you already learned).

The reason is that reading from the file system cannot work if you package your web app in a WAR. This is the proper way to package a web app. It's portable that way, because you aren't dependent on an absolute file path or the location where your app server is installed.

Iosep answered 22/2, 2010 at 1:3 Comment(3)
+1 - though "cannot work" is too strong. (Reading from the filesystem can be made to work, but doing it portably is a tricky ... and a lot more code, especially if the resource is in a JAR.)Purveyance
duffy, very nice answer and you explained what my mistake was, but BalusC went into a lot of detail - I think his answer would be helpful for people who'd like to know the inner details as well. Hope you don't mind me changing the accepted answer to his!Jerk
@Stephen - I don't think "cannot work" is too strong. Even something as simple as being deployed on two different servers with different paths to the app server will break it. The point is that you need to make your WAR as self-contained as possible. Your point is correct, but I'm going to stick to my wording.Iosep
H
16

FileInputStream will load a the file path you pass to the constructor as relative from the working directory of the Java process. Usually in a web container, this is something like the bin folder.

getResourceAsStream() will load a file path relative from your application's classpath.

Herriott answered 22/2, 2010 at 0:57 Comment(0)
S
14

The FileInputStream class works directly with the underlying file system. If the file in question is not physically present there, it will fail to open it. The getResourceAsStream() method works differently. It tries to locate and load the resource using the ClassLoader of the class it is called on. This enables it to find, for example, resources embedded into jar files.

Siberson answered 22/2, 2010 at 0:57 Comment(2)
Well, files within a jar are still physically "present" in a file system, just contained within other filesHerriott
Well, yes, of course. But they are usually not something seen as independent entities in the file system, unless you application happens to know about the jar file format and its implications. And in Java, the appropriate ClassLoader might have this knowledge, whereas a plain FileInputStream certainly has not.Siberson
A
8

classname.getResourceAsStream() loads a file via the classloader of classname. If the class came from a jar file, that is where the resource will be loaded from.

FileInputStream is used to read a file from the filesystem.

Aires answered 22/2, 2010 at 0:59 Comment(0)
H
1

I am here by separating both the usages by marking them as File Read(java.io) and Resource Read(ClassLoader.getResourceAsStream()).

File Read - 1. Works on local file system. 2. Tries to locate the file requested from current JVM launched directory as root 3. Ideally good when using files for processing in a pre-determined location like,/dev/files or C:\Data.

Resource Read - 1. Works on class path 2. Tries to locate the file/resource in current or parent classloader classpath. 3. Ideally good when trying to load files from packaged files like war or jar.

Hinz answered 16/1, 2019 at 10:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.