Read a File from the Classpath in Java: ClassLoader vs Class

Learn to read a file from the classpath in Java, how class loaders resolve names, Class vs ClassLoader, context class loader, JARs and modules.

A class loader searching target/classes, menu-data.jar and another JAR in order for specials/weekly.txt, with getResource returning the first match and resources returning all matches

Reading a file from the classpath in Java means asking a class loader for a resource by name, so Java finds the file in any folder or JAR on the classpath instead of at a fixed path on disk. To read a file from the classpath, we call getResourceAsStream() on a ClassLoader or a Class and read the returned InputStream.

We read from the classpath when a file ships together with the code, for example a default configuration inside a library JAR, an SQL script, a template or a message bundle. The code keeps working wherever the app is installed, because it never depends on the current directory.

The following example finds menu/today.txt, which sits inside a dependency JAR of a recipe box app, and reads it on Java 25.

ClassLoader loader = MenuService.class.getClassLoader();
URL where = loader.getResource("menu/today.txt");               // jar:file:/.../menu-data.jar!/menu/today.txt
boolean missing = loader.getResource("menu/tomorrow.txt") == null;   // true

String soup;
try (InputStream in = Objects.requireNonNull(loader.getResourceAsStream("menu/today.txt"), "menu/today.txt")) {
    soup = new String(in.readAllBytes(), StandardCharsets.UTF_8).strip();
}
String today = soup;                                            // "Soup of the day: tomato"

Notice that the code names only the resource, not the JAR, and that a resource that does not exist gives us null. We look at what the classpath contains, how a class loader resolves a name, which class loader to pick, resources that exist in several JARs, and how Java modules change the rules.

1. What Is the Classpath?

The classpath is the ordered list of folders and JAR files where the JVM looks for classes and resources. We set it with the -cp option of the java command, with the Class-Path entry of a JAR manifest, or let Maven, Gradle and the IDE build it from the project. The Java classpath article covers the options in detail.

For a Maven project, the runtime classpath contains target/classes (our compiled code plus the copied src/main/resources) followed by every dependency JAR. The project root folder is not on the classpath, so a file next to pom.xml cannot be found as a resource. Where the build copies resource files is shown in the resources folder guide.

A class loader searching target/classes, menu-data.jar and another JAR in order for specials/weekly.txt, with getResource returning the first match and resources returning all matches
The class loader searches the classpath entries in order; getResource() returns the first match, and resources() returns every match

2. Read a File from the Classpath with a Class Loader

A resource name is a path relative to the root of every classpath entry, with / as the separator on every operating system and no leading slash. For each entry, the class loader checks whether that entry contains the name, i.e. a file in a folder or an entry in a JAR, and the first entry that has it wins.

Before searching its own entries, a class loader asks its parent for the resource. The app class loader that loads our code has the platform class loader as its parent, so JDK resources come first, followed by our classpath in order.

The class loader offers three lookup methods.

  • getResource(name) returns a URL of the first match, or null.
  • getResourceAsStream(name) opens the first match as an InputStream, or returns null.
  • resources(name), added in Java 9, returns a Stream<URL> of every match. The older getResources(name) returns an Enumeration of the same URLs.

Say the recipe box app depends on a menu-data.jar library from another team. The library ships menu/today.txt as its default menu, and our app reads it without knowing where Maven stored the JAR. The class literal we call it on is our own service class.

class MenuService {

    static String readMenu(String name) throws IOException {
        ClassLoader loader = MenuService.class.getClassLoader();
        try (InputStream in = loader.getResourceAsStream(name)) {
            if (in == null) {
                throw new FileNotFoundException(name + " is not on the classpath");
            }
            return new String(in.readAllBytes(), StandardCharsets.UTF_8);
        }
    }
}
String menu = MenuService.readMenu("menu/today.txt").strip();   // "Soup of the day: tomato"
String none = MenuService.readMenu("menu/tomorrow.txt");         // FileNotFoundException: menu/tomorrow.txt is not on the classpath

3. Class.getResource() vs ClassLoader.getResource()

The methods on Class are thin wrappers. They first turn the name into an absolute resource name and hand the lookup to the class loader of the class (or to its module, see section 6). A name with a leading slash loses the slash. A name without it gets the package of the class as a prefix.

A JDK class shows the package rule clearly. The call on String.class with a relative name finds the class file in java/lang, whereas the same name on the class loader looks at the root.

URL relative = String.class.getResource("String.class");              // jrt:/java.base/java/lang/String.class
URL absolute = String.class.getResource("/java/lang/String.class");    // jrt:/java.base/java/lang/String.class
URL viaLoader = ClassLoader.getSystemClassLoader().getResource("String.class");   // null

Use ClassLoader methods with root-relative names, and use Class methods when the resource lives in the same package as the class. For example, a com.howtodoinjava.io.InvoiceTemplate class can load invoice.html from com/howtodoinjava/io/invoice.html with InvoiceTemplate.class.getResourceAsStream(“invoice.html”), and the file moves together with the class when we rename the package.

4. Choosing the Right Class Loader

A JVM can run several class loaders at once, and each one sees a different classpath. In a plain java -cp run, they all see our files. In Spring Boot executable JARs, Tomcat, Jakarta EE servers, test runners and plugin systems, the app classes and resources are loaded by a child class loader that the system class loader does not see.

Class loaderHow we get itWhen to use it
Loader of our classMenuService.class.getClassLoader()Default for resources that ship with our own code
Thread context class loaderThread.currentThread().getContextClassLoader()Library or framework code that must read resources of the app that calls it
System class loaderClassLoader.getSystemClassLoader(), ClassLoader.getSystemResource()Command-line tools only; misses app resources in servers and Spring Boot JARs

The context class loader exists because a library JAR on a parent class loader cannot see the classes and resources of the web app above it. Libraries such as JAXB, logging frameworks and the JDBC DriverManager read it to find files of the calling app. In our own code, the loader of our own class is the safe choice, and it gives the same result in the IDE, in tests and on the server.

ClassLoader own = MenuService.class.getClassLoader();
ClassLoader context = Thread.currentThread().getContextClassLoader();
boolean sameHere = own == context;                                      // true in a plain java -cp run
boolean contextFinds = context.getResource("menu/today.txt") != null;  // true

5. Reading a Resource That Exists in Several JARs

Two classpath entries can contain a resource with the same name. The lookup methods return the first one, so the order of the entries decides which file the app reads. When we need all copies, for example to merge configuration fragments that each JAR contributes, we call resources().

In the recipe box app, both target/classes and menu-data.jar contain specials/weekly.txt. Our own folder comes first on the classpath, so it wins.

ClassLoader loader = MenuService.class.getClassLoader();
URL first = loader.getResource("specials/weekly.txt");     // file:/.../target/classes/specials/weekly.txt

List<String> specials = new ArrayList<>();
try (Stream<URL> urls = loader.resources("specials/weekly.txt")) {
    for (URL url : urls.toList()) {
        try (InputStream in = url.openStream()) {
            specials.add(new String(in.readAllBytes(), StandardCharsets.UTF_8).strip());
        }
    }
}
List<String> allSpecials = specials;                       // [Weekly special: lasagna, Weekly special: curry]

The method URL.openStream() reads both the file: and the jar: URLs, so the loop works for folders and JARs alike. The JDK uses the same idea for META-INF/services files in ServiceLoader, where every JAR can register its own implementations.

6. Classpath Resources and Java Modules

Most apps still run on the classpath, where every resource is visible to everyone. Once a JAR becomes a named module with a module-info.java, a resource inside one of its packages is encapsulated by the module.

  • Code in the same module finds the resource with Class.getResourceAsStream(), because the lookup goes through its own module.
  • ClassLoader.getResource() finds a resource in a module package only when the package is opened unconditionally with opens in module-info.java, even for code in the same module.
  • Resources outside any package, such as files at the JAR root or in META-INF, and names ending in .class are never encapsulated.

So in modular code, we load our own resources with Class.getResourceAsStream() on a class from the same module, or we keep shared files in a folder that is not a Java package, such as menu-files/today.txt with a hyphen in the name.

7. Classpath FAQs

A wrong guess about what the classpath contains causes most of the trouble with these lookups.

7.1. Is the project root folder on the classpath?

No. Maven and Gradle put target/classes or build/classes and build/resources on the classpath, not the project folder. A file next to pom.xml is a plain file, so we open it with a path relative to the current working directory, or move it into src/main/resources.

7.2. Why does getResource() work in the IDE but return null on the server?

The usual cause is the system class loader. A call such as ClassLoader.getSystemResource(“menu/today.txt”) searches only the classpath of the java command, and in Tomcat or a Spring Boot JAR our resources are loaded by a child class loader. Calling getResource() on our own class or its class loader fixes it. A second cause is letter case. Inside a JAR, Menu/Today.txt and menu/today.txt are different names.

7.3. Can we get a File object for a classpath resource?

Only when the resource sits in a folder. Inside a JAR there is no file on disk, so new File(url.toURI()) throws IllegalArgumentException: URI is not hierarchical. We read the stream instead, or copy it to a temporary file for libraries that insist on a File.

7.4. How do we turn the stream into a String or a list of lines?

We call readAllBytes() and decode with UTF-8, as in the examples, or wrap the stream in an InputStreamReader and a BufferedReader for line-by-line reading. The InputStream to String guide compares the options, and the properties file guide covers .properties resources.

8. Conclusion

A classpath lookup finds a file by resource name in the folders and JARs that the JVM loads classes from. We pass a root-relative name such as menu/today.txt to ClassLoader.getResourceAsStream(), check for null, and read the stream with try-with-resources. Methods on Class accept a leading slash for absolute names and treat other names as relative to the package of the class.

The loader of our own class is the right default, whereas the context class loader suits library code and the system class loader suits only command-line tools. When the same name exists in several JARs, resources() returns all of them in classpath order, and in named modules Class.getResourceAsStream() is the call that sees our own packages. The Java IO tutorial lists the related file tasks.

9. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.