Is there an API to get a classpath resource (e.g. what I'd get from Class.getResource(String)
) as a java.nio.file.Path
? Ideally, I'd like to use the fancy new Path
APIs with classpath resources.
This one works for me:
return Path.of(ClassLoader.getSystemResource(resourceName).toURI());
Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
–
Waneta java.nio.file.FileSystemNotFoundException
. See stackoverflow.com/questions/22605666 –
Hamulus Guessing that what you want to do, is call Files.lines(...)
on a resource that comes from the classpath - possibly from within a jar.
Since Oracle convoluted the notion of when a Path
is a Path
by not making getResource
return a usable path if it resides in a jar file, what you need to do is something like this:
Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
class.getResource
requires a slash but getSystemResourceAsStream
can't find the file when prefixed with a slash. –
Adversity The most general solution is as follows:
interface IOConsumer<T> {
void accept(T t) throws IOException;
}
public static void processRessource(URI uri, IOConsumer<Path> action) throws IOException{
try {
Path p=Paths.get(uri);
action.accept(p);
}
catch(FileSystemNotFoundException ex) {
try(FileSystem fs = FileSystems.newFileSystem(
uri, Collections.<String,Object>emptyMap())) {
Path p = fs.provider().getPath(uri);
action.accept(p);
}
}
}
The main obstacle is to deal with the two possibilities, either, having an existing filesystem that we should use, but not close (like with file
URIs or the Java 9’s module storage), or having to open and thus safely close the filesystem ourselves (like zip/jar files).
Therefore, the solution above encapsulates the actual action in an interface
, handles both cases, safely closing afterwards in the second case, and works from Java 7 to Java 18. It probes whether there is already an open filesystem before opening a new one, so it also works in the case that another component of your application has already opened a filesystem for the same zip/jar file.
It can be used in all Java versions named above, e.g. to list the contents of a package (java.lang
in the example) as Path
s, like this:
processRessource(Object.class.getResource("Object.class").toURI(),new IOConsumer<Path>(){
public void accept(Path path) throws IOException {
try(DirectoryStream<Path> ds = Files.newDirectoryStream(path.getParent())) {
for(Path p: ds)
System.out.println(p);
}
}
});
With Java 8 or newer, you can use lambda expressions or method references to represent the actual action, e.g.
processRessource(Object.class.getResource("Object.class").toURI(), path -> {
try(Stream<Path> stream = Files.list(path.getParent())) {
stream.forEach(System.out::println);
}
});
to do the same.
The final release of Java 9’s module system has broken the above code example. The Java versions from 9 to 12 inconsistently return the path /java.base/java/lang/Object.class
for Paths.get(Object.class.getResource("Object.class"))
whereas it should be /modules/java.base/java/lang/Object.class
. This can be fixed by prepending the missing /modules/
when the parent path is reported as non-existent:
processRessource(Object.class.getResource("Object.class").toURI(), path -> {
Path p = path.getParent();
if(!Files.exists(p))
p = p.resolve("/modules").resolve(p.getRoot().relativize(p));
try(Stream<Path> stream = Files.list(p)) {
stream.forEach(System.out::println);
}
});
Then, it will again work with all versions and storage methods. Starting with JDK 13, this work-around is not necessary anymore.
It turns out you can do this, with the help of the built-in Zip File System provider. However, passing a resource URI directly to Paths.get
won't work; instead, one must first create a zip filesystem for the jar URI without the entry name, then refer to the entry in that filesystem:
static Path resourceToPath(URL resource)
throws IOException,
URISyntaxException {
Objects.requireNonNull(resource, "Resource URL cannot be null");
URI uri = resource.toURI();
String scheme = uri.getScheme();
if (scheme.equals("file")) {
return Paths.get(uri);
}
if (!scheme.equals("jar")) {
throw new IllegalArgumentException("Cannot convert to Path: " + uri);
}
String s = uri.toString();
int separator = s.indexOf("!/");
String entryName = s.substring(separator + 2);
URI fileURI = URI.create(s.substring(0, separator));
FileSystem fs = FileSystems.newFileSystem(fileURI,
Collections.<String, Object>emptyMap());
return fs.getPath(entryName);
}
Update:
It’s been rightly pointed out that the above code contains a resource leak, since the code opens a new FileSystem object but never closes it. The best approach is to pass a Consumer-like worker object, much like how Holger’s answer does it. Open the ZipFS FileSystem just long enough for the worker to do whatever it needs to do with the Path (as long as the worker doesn’t try to store the Path object for later use), then close the FileSystem.
newFileSystem
can lead to multiple resources hanging around open for ever. Although @Lumberman addendum avoids the error when trying to create an already created file system, if you try to use the returned Path
you will get a ClosedFileSystemException
. @Behring response works well for me. –
Footwear FileSystem
. If you load a resource from a Jar, and you then create the required FileSystem
- the FileSystem
will also allow you to load other resources from the same Jar. Also, once you created the new FileSystem
you can just try to load the resource again using Paths.get(Path)
and the implementation will automatically use the new FileSystem
. –
Bathe #getPath(String)
method on the FileSystem
object. –
Bathe Path
object is also very unusable if you close the FileSystem
, so depending on how your code is structured it can become very ugly. For example - you can't have a utility method returning a Path
and close the FileSystem
inside the utility method, because using the Path
object will then throw errors due to the underlying FileSystem
being closed. –
Bathe FileSystems#newFileSystem
is, so you might also want to consider some synchronization so that multiple threads don't try and create FileSystems
at the same time. –
Bathe Files#isRegularFile(Path)
does not return true
for resources within Jar files.. –
Bathe I wrote a small helper method to read Paths
from your class resources. It is quite handy to use as it only needs a reference of the class you have stored your resources as well as the name of the resource itself.
public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException {
URL url = resourceClass.getResource(resourceName);
return Paths.get(url.toURI());
}
You can not create URI from resources inside of the jar file. You can simply write it to the temp file and then use it (java8):
Path path = File.createTempFile("some", "address").toPath();
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);
Read a File from resources folder using NIO, in java8
public static String read(String fileName) {
Path path;
StringBuilder data = new StringBuilder();
Stream<String> lines = null;
try {
path = Paths.get(Thread.currentThread().getContextClassLoader().getResource(fileName).toURI());
lines = Files.lines(path);
} catch (URISyntaxException | IOException e) {
logger.error("Error in reading propertied file " + e);
throw new RuntimeException(e);
}
lines.forEach(line -> data.append(line));
lines.close();
return data.toString();
}
You need to define the Filesystem to read resource from jar file as mentioned in https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html. I success to read resource from jar file with below codes:
Map<String, Object> env = new HashMap<>();
try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {
Path path = fs.getPath("/path/myResource");
try (Stream<String> lines = Files.lines(path)) {
....
}
}
© 2022 - 2024 — McMap. All rights reserved.
Paths.get(URI)
, then ´URL.toURI(), and last
getResource()` which returns aURL
. You might be able to chain those together. Haven´t tried though. – Anew