48

I have a Maven project and inside a method I want to create a path for a directory in my resources folder. This is done like this:

try {
    final URI uri = getClass().getResource("/my-folder").toURI();
    Path myFolderPath = Paths.get(uri);
} catch (final URISyntaxException e) {
    ...
}

The generated URI looks like jar:file:/C:/path/to/my/project.jar!/my-folder.

The stacktrace is as following:

Exception in thread "pool-4-thread-1" java.nio.file.FileSystemNotFoundException
    at com.sun.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:171)
    at com.sun.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:157)
    at java.nio.file.Paths.get(Paths.java:143)

The URI seems to be valid. The part before ! points to the generated jar-file and the part after it to my-folder in the root of the archive. I have used this instructions before to create paths to my resources. Why am I getting an exception now?

Neuron
  • 3,776
  • 3
  • 24
  • 44
stevecross
  • 5,130
  • 6
  • 37
  • 79
  • Is the Zip file in a format readable by the ZipFileSystemProvider? – Uwe Allner Jul 30 '14 at 09:15
  • 1
    The folder exists in the root. And the jar-file is generated by Maven, thus it should be readable. – stevecross Jul 30 '14 at 09:18
  • If you already have the `URL`, you don't need a `File` or `Path` just to read its contents. You can just call `URL.openStream()` and read from the `InputStream` that method returns. If you actually *must* have a `File` or `Path` object, then you'll need the `FileSystem` fixes mentioned below. But most things don't really need a file and can handle `InputStream` or `Reader` interfaces, so I'd suggest going down the `URL.openStream()` route first if possible. – Shadow Man Jun 07 '19 at 18:22

4 Answers4

63

You need to create the file system before you can access the path within the zip like

final URI uri = getClass().getResource("/my-folder").toURI();
Map<String, String> env = new HashMap<>(); 
env.put("create", "true");
FileSystem zipfs = FileSystems.newFileSystem(uri, env);
Path myFolderPath = Paths.get(uri);

This is not done automatically.

See http://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html

Uwe Allner
  • 3,057
  • 7
  • 31
  • 42
  • I am using the same technique to access a jar file. But the problem that I am facing is that whenever there is a space char in the path, I get the same error. It works fine otherwise – frewper Jan 18 '19 at 11:15
  • Yes, spaces and characters beyond the ASCII range in file paths cause problems with the nio. Also when files in the jar/zip have these characters. But this is a different problem, You may find help here: https://stackoverflow.com/questions/37936627/uri-to-file-in-zip-incorrect-if-path-contains-spaces – Uwe Allner Jan 21 '19 at 08:06
13

If you intend to read the resource file, you can directly use getClass.getResourceAsStream. This will set up the file system implictly. The function returns null if your resource could not be found, otherwise you directly have an input stream to parse your resource.

user5922822
  • 169
  • 1
  • 5
12

Expanding on @Uwe Allner 's excellent answer, a failsafe method to use is

private FileSystem initFileSystem(URI uri) throws IOException
{
    try
    {
        return FileSystems.getFileSystem(uri);
    }
    catch( FileSystemNotFoundException e )
    {
        Map<String, String> env = new HashMap<>();
        env.put("create", "true");
        return FileSystems.newFileSystem(uri, env);
    }
}

Calling this with the URI you are about to load will ensure the filesystem is in working condition. I always call FileSystem.close() after using it:

FileSystem zipfs = initFileSystem(fileURI);
filePath = Paths.get(fileURI);
// Do whatever you need and then close the filesystem
zipfs.close();
mvreijn
  • 2,462
  • 23
  • 37
10

In addition to @Uwe Allner and @mvreijn:

Be careful with the URI. Sometimes the URI has a wrong format (e.g. "file:/path/..." and correct one would be "file:///path/...") and you cant get a proper FileSystem.
In this case it helps that the URI is created from the Path's toUri() method.

In my case I modified initFileSystem method a little bit and used in non exceptional cases the FileSystems.newFileSystem(uri, Collections.emptyMap()). In exceptional case the FileSystems.getDefault() is used.

In my case it was also necessary to catch the IllegalArgumentException to handle the case with Path component should be '/'. On windows and linux the exception is caught but the FileSystems.getDefault() works. On osx no exception happens and the newFileSystem is created:

private FileSystem initFileSystem(URI uri) throws IOException {
    try {
        return FileSystems.newFileSystem(uri, Collections.emptyMap());
    }catch(IllegalArgumentException e) {
        return FileSystems.getDefault();
    }
}
schlegel11
  • 356
  • 4
  • 7