| JVM | Platform | Status |
|---|---|---|
| OpenJDK (Temurin) Current | Linux | |
| OpenJDK (Temurin) LTS | Linux | |
| OpenJDK (Temurin) Current | Windows | |
| OpenJDK (Temurin) LTS | Windows |
The fsbind package implements a JSR203
filesystem that can bind existing JSR203 filesystems into a single unified
namespace. It is similar in design and goals to
PhysicsFS.
- Bind multiple JSR203 filesystems into a single read-only filesystem.
- Written in pure Java 21.
- OSGi ready.
- JPMS ready.
- ISC license.
- High-coverage automated test suite.
The fsbind package provides a read-only JSR203 filesystem accessible via an
fsbind URI scheme. To create a filesystem named example, call:
final FBFilesystem filesystem =
(FBFilesystem) FileSystems.newFileSystem(URI.create("fsbind:example:/"), null);
The fsbind filesystem exposes a strict, read-only, in-memory UNIX-like virtual
filesystem consisting of virtual directories and other JSR203 filesystems
mounted into filesystem tree.
For example, to create a directory hierarchy and mount a zip file into it, it's first necessary to create a directory in the filesystem. This directory is virtual in the sense that it is a complete in-memory fabrication and does not actually involve any file I/O:
final var dir = filesystem.getPath("/", "archives", "zip0");
Files.createDirectory(dir);
Then, we can take an existing ZipFS filesystem and mount it into the virtual filesystem:
final var zipfs =
FileSystems.newFileSystem(
URI.create("/path/to/some/zip_file.zip"), Map.of(), null
);
filesystem.mount(
new MountRequest(zipfs.getPath("/"),
dir
);
This will make the contents of /path/to/some/zip_file.zip accessible at
/archives/zip0 inside the virtual filesystem. It is also possible to expose
only part of the archive by using a non-root path as the first argument to
the MountRequest. For example, if zip_file.zip looks like this:
Length Date Time Name
--------- ---------- ----- ----
0 2025-04-10 20:16 x/
10 2025-04-10 20:16 x/a.txt
10 2025-04-10 20:16 x/c.txt
10 2025-04-10 20:16 x/b.txt
0 2025-04-10 20:16 y/
10 2025-04-10 20:16 y/a.txt
0 2025-04-10 20:16 z/
--------- -------
40 7 files
We may choose to only expose the contents of the x directory:
filesystem.mount(
new MountRequest(zipfs.getPath("/x"),
dir
);
We would then be able to access x/a.txt by opening /archives/zip0/a.txt:
Files.readString(dir.resolve("a.txt"));
The fsbind filesystem is a minimal abstraction over existing JSR203
filesystems and, in most cases, simply delegates operations to those
filesystems directly. Operations that imply write access will typically
result in a FilesystemException being raised.
The semantics of real filesystems are generally not very well documented, and
filesystems differ immensely across platforms. Furthermore, JSR203 filesystem
implementations have a great deal of freedom in how they're implemented
internally, and a fsbind filesystem usually consists of a set of filesystems
which may have very different semantics individually placed into a single
unified namespace. Additionally, fsbind takes a much stricter view with
respect to paths than JSR203 filesystems usually do. You can, if
you are particularly unhinged, mount an fsbind filesystem inside an fsbind
filesystem (although they are required to be different instances). This is
not recommended.
Therefore, assume the following rules of thumb:
- If you know a directory is a virtual directory that you created, and you haven't mounted anything over the top of it, then you can safely create directories inside it.
- You can delete an empty directory that you created.
- You can only rely on BasicFileAttributes being available. Trying to use anything else will almost certainly result in an exception.
- You cannot create files.
- You cannot modify files (including setting file attributes and/or renaming).
- You cannot delete files.
- You cannot create directories inside mounted filesystems.
- You cannot delete directories inside mounted filesystems.
- You can use the WatchService, but it might not be particularly efficient compared to native services.
- You should use absolute paths as much as possible; treat relative paths as intermediate values and avoid passing them to the filesystem. Most operations require absolute paths and will raise exceptions if presented with relative paths.
An fsbind filesystem uses UNIX-like paths. Outside of mounted filesystems,
paths are case-insensitive. All resources, whatever the underlying filesystem,
are accessed with fsbind paths.
The fsbind filesystem has no concept of a "current directory" and therefore
the provider requires the use of absolute instead of relative paths in most
instances.
A path component is a string not containing / and not equal to ., ..,
or ....
An absolute path is the string "/" followed by a list of path components.
A relative path is a non-empty list of path components.
As examples, the following paths are all valid:
/
/a
/CAS.qterm/CyberAcme Systems/pty0
a/b/c
a
As examples, the following paths are not valid:
/.
/..
/CAS.qterm/.../pty0
.
The fsbind filesystem provides a simple-minded WatchService
implementation that spawns one virtual thread per registered path, and
checks the modification times on those paths at a configurable rate in order
to deliver watch events.
It's possible to specify the rate that will be used by providing a configuration value in the environment map used to create the filesystem:
FileSystems.newFileSystem(
"fsbind:example:/",
Map.of(
FBFilesystemProvider.environmentWatchServiceDurationKey(),
Duration.ofMillis(250L)
)
);
The provided Duration value is the amount of time that each virtual thread
will pause between file checks. Smaller values will check more often, publish
events more promptly, and entail more file I/O.
Note that this is unlikely to be anywhere near as performant as the native
WatchService provided by the JVM for real filesystems.
