2

I need to get a resource from inside the root of the application when its packed into jar. My project is like this:

ProjectRoot
  resource.txt //want to access from here
  src
    main
       java
         package1
           package2
              package3
                 Main.java
  target
    app.jar
    classes
         resource.txt //works when here
         package1
           package2
              package3
                 Main.class

I use this code:

Path path = Paths.get("resource.txt");

When run before packaging into a jar, it finds the file just fine (inside ProjectRoot). When running the jar, it can't find it, and transforms this path to target/resource.txt.

This code:

BufferedReader br = new BufferedReader(new InputStreamReader(new Main().getClass().getClassLoader().getResourceAsStream(
                "resource.txt")));

when run before packaging looks for the resource inside target/classes. After packaging it claims to taking the resource from .../target/app.jar!/resource.txt.

This code:

BufferedReader br = new BufferedReader(new InputStreamReader(new Main().getClass().getClassLoader().getResourceAsStream(
                    "/resource.txt")));

I can't understand where's looking for the resource, but it doesn't seem to be ProjectRoot.

All I want to do is to place the resource inside ProjectRoot and be able to access it from both outside jar (when running the class files from IDE) and inside (after having packaged the files into a jar file using Maven).

EDIT: I NEED THE CODE TO WORK BOTH FOR PRE- AND POST- packaging. MEANING: If I run a Main.java FROM INSIDE IDE IT WOULD GET THE RESOURCE; IF I PACKAGE EVERYTHING INTO JAR AND RUN JAR IT WOULD GET THE RESOURCE - ALL WITH THE SAME CODE.

parsecer
  • 4,758
  • 13
  • 71
  • 140
  • In Maven, resources go under `src/main/resources`. Otherwise they're not copied to the output directory or embedded into the JAR file and thus don't end up on the classpath at runtime. – Slaw Jun 26 '19 at 19:43
  • @Slaw I started that way. I didn't find a way to right the code that would be both able to access that folder before and after packaging. – parsecer Jun 26 '19 at 20:08
  • If it's directly under `src/main/resources` then `getResource("/resource.txt")` should work. – Slaw Jun 26 '19 at 20:10
  • @Slaw what is the full code? `Main.class.getResource("/resource.txt")`? – parsecer Jun 26 '19 at 20:13
  • 1
    That should work. Note the leading `/` makes it an absolute path, whereas no leading `/` would make it relative to the location of the `Class` you call `getResource` on. And remember, resources are searched for on the _classpath_. – Slaw Jun 26 '19 at 20:16
  • @Slaw no, it looks inside `ProjectRoot/target/classes` – parsecer Jun 26 '19 at 20:23
  • Yes, because when Maven executes the code against `target/classes` that's the root of the classpath. Once you package the application in a JAR file and execute against said JAR file, it will look in the JAR file for the resource. That's why this API uses the classpath... it's a code-location-independent way to retrieve resources. – Slaw Jun 26 '19 at 20:24
  • @Slaw how to I get the resource from inside `ProjectRoot` or at least `ProjecRoot/src/main/resources` if it only looks at `ProjectRoot/target/classes`? – parsecer Jun 26 '19 at 21:52
  • 1
    The files in `src/main/resources` are _copied_ into `target/classes` during one of the phases of Maven. When packaging into a JAR, the files in `target/classes` are all added to the JAR. – Slaw Jun 26 '19 at 22:03
  • Your edit doesn't change anything I've been saying. – Slaw Jun 26 '19 at 22:22

3 Answers3

2

Use: Main.class.getResource("/resource.txt").

Note that your attempt using any call to getClassLoader is strictly worse (it's more text, and will fail more often, because that class loader can in exotic cases be null (specifically, when you're part of the bootstrap loader), whereas calling getResource directly on the class always works.

The reason your snippet does not work is because when invoking getResource on the classloader, you must NOT start the resource with a slash. When invoking on a class directly, you can (if you don't, it'll be relative to the package of the class you're calling it on, if you do, it'll be relative to the root).

TL;DR: Of the forms SomeClass.class.getClassLoader().getResource, getClass().getResource and MyClass.class.getResource, only the last one is correct, the rest are strictly inferior and therefore should not be used at all.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • `Main.class.getResource("/resource.txt")` looks inside `ProjectRoot/target/app.jar!/resource.txt` instead of just `ProjectRoot`. – parsecer Jun 26 '19 at 20:11
  • @parsecer That's what should happen if `resource.txt` is actually supposed to be a _resource_. – Slaw Jun 26 '19 at 20:17
  • Of course, that _is_ the root. Just like Main.class is copied into that jar, that resource is as much part of your application as that class file is, and it should be inside the jar. And then Main.class.getResource("/resource.txt") will find it for you. – rzwitserloot Jun 27 '19 at 18:11
2

Maven uses something called the Standard Directory Layout. If you don't follow this layout then the plugins can't do their job correctly. Technically, you can configure Maven to use different directories but 99.999% of the time this is not necessary.

One of the features of this layout is that production files go in:

  • <project-dir>/src/main/java
    • All *.java files
  • <project-dir>/src/main/resources
    • All non-*.java files (that are meant to be resources)

When you build your project the Java source files are compiled and the *.class files are put into the target/classes directory; this is done by the maven-compiler-plugin. Meanwhile, the resource files are copied from src/main/resources into target/classes as well; the maven-resources-plugin is responsible for this.

Note: See Introduction to the Build Lifecycle for more information about phases and which plugins are executed by which phase. This Stack Overflow question may also be useful.

When you launch your application from the IDE (possibly via the exec-maven-plugin) the target/classes directory is put on the classpath. This means all the compiled classes from src/main/java and all the copied resources from src/main/resources are available to use via the classpath.

Then, when you package your application in a JAR file, all the files in target/classes are added to the resulting JAR file (handled by the maven-jar-plugin). This includes the resources copied from src/main/resources. When you launch the application using this JAR file the resources are still available to use via the classpath, because they're embedded in the JAR file.

To make resource.txt available on the classpath, just move:

<project-dir>/resource.txt

To:

<project-dir>/src/main/resources/resource.txt.

Then you can use Class#getResource with /resource.txt as the path and everything should work out for you. The URL returned by getResource will be different depending on if you're executing against target/classes or against the JAR file.

When executing against target/classes you'll get something like:

file:///.../<project-dir>/target/classes/resource.txt

When executing against the JAR file you'll get something like:

jar:file:///.../<project-dir>/target/projectname-version.jar!/resource.txt

Note: This all assumes resource.txt is actually supposed to be a resource and not an external file. Resources are typically read-only once deployed in a JAR file; if you need a writable file then it's up to you to use a designated location for the file (e.g. a folder in the user's home directory). One typically accesses external files via either java.io.File or java.nio.file.*. Remember, resources are not the same thing as normal files.

Now, if you were to put resource.txt directly under <project-dir> that would mean nothing to Maven. It would not be copied to target/classes or end up in the JAR file which means the resource is never available on the classpath. So just to reiterate, all resources go under src/main/resources.


Check out the Javadoc of java.lang.Class#getResource(String) for more information about the path, such as when to use a leading / and when not to. The link points to the Javadoc for Java 12 which includes information about resources and modules (JPMS/Jigsaw modules, not Maven modules); if you aren't using modules you can ignore that part of the documentation.

Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Made it work, thanks. To future readers: eventually I used this code to open files located in `src/main/resources`:` `BufferedReader br = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream("/resource.txt")));`. After `package` Maven command is run, everything inside `src/main/resources` gets copypasted inside `target/classes`. That's where `Main.java` from IDE takes the file from. At the same time, `package` command creates a jar that takes files from `target/classes` and places those inside its root. – parsecer Jul 02 '19 at 20:25
0

You can use this solution for both before and after jar creation. You also need to add "commons-io" as dependency.

InputStream inputStream = null;
    File targetFile;
    try {
        ClassPathResource classPathResource = new ClassPathResource("jasper/history.jrxml");
        inputStream = classPathResource.getInputStream();
        targetFile = File.createTempFile("test", ".txt");
        FileUtils.copyInputStreamToFile(inputStream, targetFile);
    } catch (Exception e) {
        logger.error("Exception occurred. Exception: {}", e);
    } finally {
        IOUtils.closeQuietly(inputStream);
    }
Rafsan Jany
  • 53
  • 2
  • 8