Classpath resource within jar
Use getResource
instead of getSystemResource
to use a resource specific to a given classloader instead of the system. For example, try any of the following:
URL resource = getClass().getClassLoader().getResource("R.txt");
URL resource = Foo.class.getClassLoader().getResource("R.txt");
URL resource = getClass().getResource("/R.txt");
URL resource = Foo.class.getResource("/R.txt");
Note the leading slash when calling Class.getResource
instead of ClassLoader.getResource
; Class.getResource
is relative to the package containing the class unless you have a leading slash, whereas ClassLoader.getResource
is always absolute.
Reading a resource file from within jar
Rather than trying to address the resource as a File just ask the ClassLoader to return an InputStream for the resource instead via getResourceAsStream:
try (InputStream in = getClass().getResourceAsStream("/file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
// Use resource
}
As long as the file.txt
resource is available on the classpath then this approach will work the same way regardless of whether the file.txt
resource is in a classes/
directory or inside a jar
.
The URI is not hierarchical
occurs because the URI for a resource within a jar file is going to look something like this: file:/example.jar!/file.txt
. You cannot read the entries within a jar
(a zip
file) like it was a plain old File.
This is explained well by the answers to:
- How do I read a resource file from a Java jar file?
- Java Jar file: use resource errors: URI is not hierarchical
Getting resource file from inside jar
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
- All
<project-dir>/src/main/resources
- All non-
*.java
files (that are meant to be resources)
- All non-
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.
Classpath resource not found when running as jar
resource.getFile()
expects the resource itself to be available on the file system, i.e. it can't be nested inside a jar file. This is why it works when you run your application in STS (Spring Tool Suite) but doesn't work once you've built your application and run it from the executable jar. Rather than using getFile()
to access the resource's contents, I'd recommend using getInputStream()
instead. That'll allow you to read the resource's content regardless of where it's located.
How to get a path to a resource in a Java JAR file
This is deliberate. The contents of the "file" may not be available as a file. Remember you are dealing with classes and resources that may be part of a JAR file or other kind of resource. The classloader does not have to provide a file handle to the resource, for example the jar file may not have been expanded into individual files in the file system.
Anything you can do by getting a java.io.File could be done by copying the stream out into a temporary file and doing the same, if a java.io.File is absolutely necessary.
How can I read a file from the classpath in a JAR?
You cant refer the file from jar the way you do it in from resources. Since the file is packaged inside the jar you need to read it as resource.
You have to read the file as resource using classloader.
sample code:
ClassLoader CLDR = this.getClass().getClassLoader();
InputStream inputStream = CLDR.getResourceAsStream(filePath);
If you are using java 8 and above then you can use below code using nio to read your file:
final Path path = Paths.get(Main.class.getResource(fileName).toURI());
final byte[] bytes = Files.readAllBytes(path);
String fileContent = new String(bytes, CHARSET_ASCII);
Access resources from another jar file
Solution 1
Use a classpath wildcard.
jsvc -cp globalclasspath/*:daemons/service.jar (...)
See "How to use a wildcard in the classpath to add multiple jars?"
Solution 2
To read data in JARs not on the classpath, use URLClassLoader
. The general algorithm is this:
- Find the list of JARs in the
globalclasspath
directory. - Create a
URLClassLoader
from this list of JARs. - Look up the resource you want from the
URLClassLoader
instance.
To find JARs on the classpath, I used ResourceList
from the StackOverflow article "Get a list of resources from classpath directory."
public class MyClass {
/**
* Creates a {@code URLClassLoader} from JAR files found in the
* globalclasspath directory, assuming that globalclasspath is in
* {@code System.getProperty("java.class.path")}.
*/
private static URLClassLoader createURLClassLoader() {
Collection<String> resources = ResourceList.getResources(Pattern.compile(".*\\.jar"));
Collection<URL> urls = new ArrayList<URL>();
for (String resource : resources) {
File file = new File(resource);
// Ensure that the JAR exists
// and is in the globalclasspath directory.
if (file.isFile() && "globalclasspath".equals(file.getParentFile().getName())) {
try {
urls.add(file.toURI().toURL());
} catch (MalformedURLException e) {
// This should never happen.
e.printStackTrace();
}
}
}
return new URLClassLoader(urls.toArray(new URL[urls.size()]));
}
public static void main(String[] args) {
URLClassLoader classLoader = createURLClassLoader();
System.out.println(classLoader.getResource("mine.properties"));
}
}
I ran the following command:
java -cp globalclasspath:daemons/service.jar MyClass
The terminal output:
jar:file:/workspace/all/globalclasspath/data.jar!/mine.properties
Classpath including JAR within a JAR
If you're trying to create a single jar that contains your application and its required libraries, there are two ways (that I know of) to do that. The first is One-Jar, which uses a special classloader to allow the nesting of jars. The second is UberJar, (or Shade), which explodes the included libraries and puts all the classes in the top-level jar.
I should also mention that UberJar and Shade are plugins for Maven1 and Maven2 respectively. As mentioned below, you can also use the assembly plugin (which in reality is much more powerful, but much harder to properly configure).
Related Topics
Freemarker Iterating Over Hashmap Keys
Invalid Syntax Error "Type= Myisam" in Ddl Generated by Hibernate
How to Do N-Level Nested Loops in Java
How to Declare a 2D String Arraylist
Detect Enter Press in Jtextfield
Console Application with Java and Gradle
Change Database Schema Used by Spring Boot
Differencebetween Canonical Name, Simple Name and Class Name in Java Class
JPA How to Make Composite Foreign Key Part of Composite Primary Key
What in the World Are Spring Beans
Locally Declared Variables Can Not Be Inspected
Differencebetween Lowagie and Itext
What Is the Use of Filter and Chain in Servlet
A Simple Http Server with Java/Socket
Why Did Servlet.Service() for Servlet Jsp Throw This Exception