{"id":19270437,"url":"https://github.com/xpipe-io/modulefs","last_synced_at":"2025-07-07T15:41:10.667Z","repository":{"id":142948183,"uuid":"360884066","full_name":"xpipe-io/modulefs","owner":"xpipe-io","description":"A file system implementation to access the contents of Java modules in a unified way.","archived":false,"fork":false,"pushed_at":"2024-11-23T15:18:39.000Z","size":137,"stargazers_count":40,"open_issues_count":0,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-07-04T12:15:33.865Z","etag":null,"topics":["filesystem","io","java","java-17","java-library","jigsaw","jlink","jpms","jrtfs","library","module","modules","nio","nio2"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xpipe-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-04-23T12:55:15.000Z","updated_at":"2025-06-02T10:39:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"4576d955-bfb2-48f2-bf53-893dfe8a1174","html_url":"https://github.com/xpipe-io/modulefs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/xpipe-io/modulefs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xpipe-io%2Fmodulefs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xpipe-io%2Fmodulefs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xpipe-io%2Fmodulefs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xpipe-io%2Fmodulefs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xpipe-io","download_url":"https://codeload.github.com/xpipe-io/modulefs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xpipe-io%2Fmodulefs/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264104466,"owners_count":23558142,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["filesystem","io","java","java-17","java-library","jigsaw","jlink","jpms","jrtfs","library","module","modules","nio","nio2"],"created_at":"2024-11-09T20:24:51.188Z","updated_at":"2025-07-07T15:41:10.639Z","avatar_url":"https://github.com/xpipe-io.png","language":"Java","readme":"[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.xpipe/modulefs/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.xpipe/modulefs)\n[![javadoc](https://javadoc.io/badge2/io.xpipe/modulefs/javadoc.svg)](https://javadoc.io/doc/io.xpipe/modulefs)\n[![Build Status](https://github.com/xpipe-io/modulefs/actions/workflows/publish.yml/badge.svg)](https://github.com/xpipe-io/modulefs/actions/workflows/publish.yml)\n\n# ModuleFS library\n\nThe ModuleFS library provides a simple file system implementation to access the contents of Java modules in a unified way.\nIt also comes with a variety of neat features that will make working with modules more enjoyable for you.\nYou can get the library through [maven central](https://search.maven.org/artifact/io.xpipe/modulefs).\nNote that at least Java 17 is required as it is the first LTS release that includes all necessary bug fixes for the internal module file systems.\n\n## Installation\n\nTo use ModuleFS with Maven you have to add it as a dependency:\n\n    \u003cdependency\u003e\n      \u003cgroupId\u003eio.xpipe\u003c/groupId\u003e\n      \u003cartifactId\u003emodulefs\u003c/artifactId\u003e\n      \u003cversion\u003e0.1.6\u003c/version\u003e\n    \u003c/dependency\u003e\n\nFor gradle, add the following entries to your build.gradle file:\n\n    dependencies {\n        implementation group: 'io.xpipe', name: 'modulefs', version: '0.1.6'\n    }\n\nAdd the library to your project's module-info like this:\n\n    requires io.xpipe.modulefs;\n\nNote that ModuleFS requires your project to be modularized.\n\n## Motivation\n\nThe [Path](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/file/Path.html) and\n[FileSystem](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/file/FileSystem.html) APIs\nintroduced in Java 7 with NIO.2 makes working with files much more pleasant.\nOne hurdle when trying to use these APIs with bundled application resources is that modules can be stored in different formats:\n- Exploded module directory, stored as directory tree (Used during development)\n- Module artifacts, mostly stored as jars (Used for both development and production)\n- Modules contained in a jlink image, which are stored in a proprietary jimage format (Only used for production)\n\n#### Implementation Differences\n\nWhile there are FileSystem implementations like `jdk.zipfs` for jar files and the internal `jrtfs` for jlink images,\nthere is no unified interface, which results in you having to take care of FileSystem specific differences.\nFor example, the first challenge is to reliably find out the storage format of a module.\nThen, you also have to adapt your code to handle unique properties of the storage format.\nFor example, every format requires you to use different schemes and approaches to closing the underlying file\nsystem, which causes exceptions when not being done properly.\nAs a result, how to access files in modules using FileSystems heavily depends on how the modules are stored\nand can therefore vary between development and production environments.\nThe current solution to reliably access resources in a package of a jar, or now a module, is to use the old methods\n[getResource()](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getResource(java.lang.String)) and\n[getResourceAsStream()](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getResourceAsStream(java.lang.String)).\nThese methods are however way more tedious to work with as it is lacking the many features of the Path API.\n\n#### Modules vs Classpaths\n\nThe main difference between the traditional classpaths and\nmodules is that a module is located at exactly one location, e.g. a jar archive or a directory.\nIt is not possible to combine multiple different locations into\none module as you could do with for example multiple directories and classpaths.\nThis makes working with FileSystems on modules much easier as there is only one root.\n\n\n## Features\n\nModuleFS allows you to create a FileSystem using the `module` URI scheme.\nThe created FileSystem instance conforms to standard FileSystem behaviours\nwith some limitations on writability as modules are intended to be read-only.\nA simple file reading example could look like this:\n\n````java\ntry (var fs = FileSystems.newFileSystem(\n        URI.create(\"module:/com.myorg.mymodule\"), Map.of())) {\n    // The file system paths start from the root of the module,\n    // so you have to include packages in your paths!\n    var filePath = fs.getPath(\"com/myorg/mymodule/test_resource.txt\");\n    var fileContent = Files.readString(filePath);\n}\n````\n\nThe Path API allows for more complex applications than just parsing the contents of a single file.\nFor example, we can also easily recursively iterative over all files in a directory that exists inside your module:\n\n````java\ntry (var fs = FileSystems.newFileSystem(\n        URI.create(\"module:/com.myorg.mymodule\"), Map.of())) {\n    \n    var filePath = fs.getPath(\"com/myorg/mymodule/assets\");\n    Files.walkFileTree(filePath, new SimpleFileVisitor\u003c\u003e() {\n        @Override\n        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\n            // Do anything you want with the file\n            return FileVisitResult.CONTINUE;\n        }\n    });\n}\n````\n\nBasically, you can make use of any method in the\n[Files](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/file/Files.html) class.\n\n\n### Module Layers\n\nIn case you are using custom [ModuleLayers](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ModuleLayer.html),\nyou will have to pass this information to the file system to correctly locate all modules of this layer:\n\n````java\nModuleLayer customLayer = ... ;\ntry (var fs = FileSystems.newFileSystem(\n        URI.create(\"module:/com.myorg.mymodule\"), Map.of(\"layer\", customLayer))) {\n    ...\n}\n````\n\n### Using URLs\n\nMany other Java methods take [URLs](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/net/URL.html) as an input.\nOne problem with many libraries, even standard libraries, is that they hardcode/expect\nURLs with a certain protocol, e.g. `file:` or `jar:`.\nTo adapt ModuleFS to this limitation, the ModuleFS library does not come with custom\n[URLStreamHandler](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/net/URLStreamHandler.html)\nimplementations required to make use of `module: ...` URLs.\n\nThe easiest way of obtaining a usable URL is to access the wrapped internal Path and access its URL:\n\n````java\ntry (ModuleFileSystem fs = ModuleFileSystem.create(\"module:/com.myorg.mymodule\")) {\n    ModulePath modulePath = fs.getPath(\"com/myorg/mymodule/test_resource.txt\");\n    // Get the internal path of the module path\n    Path internalPath = modulePath.getWrappedPath();\n    URL usableUrl = internalPath.toUri().toURL();\n}\n````\n\nIn the above example, we explicitly use the ModuleFileSystem class to automatically get ModulePath instances when creating paths.\nThis allows us to use the `getWrappedPath()` method to obtain the internal path, which returns an `file:`, `jar:`, or `jrt:` URL.\nYou can then use this URL to access any resources of the module in a normal fashion by passing the URL.\nNote that this requires a file system to be created through the `ModuleFileSystem` class, not the `FileSystem` class.\n\n### Bypassing Encapsulation\n\nOne common problem you might encounter when working with modules our permission issues.\nUnless a package is open by a certain module, you are not allowed to access the contained resources.\nHowever, this mechanism is implemented on a very high level,\ni.e. you can easily bypass it by accessing the underlying file system instead methods like\n[getResource()](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getResource(java.lang.String)).\nAs ModuleFS does only work through the underlying file systems,\nyou will not run into any permission issues when using ModuleFS, i.e.\nyou can even access resources from modules that are not open at all.\n\n### Module References\n\nIn case you are loading modules at runtime and want to access the file system of a module before a proper module layer is created,\nyou can also create a module file system for a\n[ModuleReference](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ModuleReference.html) like this:\n\n````java\nPath path = ...;\nvar finder = ModuleFinder.of(path);\nvar moduleReference = finder.find(\"myorg.mymodule\")\n        .orElseThrow(() -\u003e new IllegalArgumentException(\"Module not found\"));\ntry (var fs = ModuleFileSystem.create(moduleReference)) {\n    ...\n}\n````\n\n\n## Development\n\nTesting is made more difficult by the fact that we also have to run all tests in a jlink image to achieve good coverage.\nFurthermore, junit uses the exploded module structure, so we also have to separately run tests in a module jar file.\nTherefore, the tests are defined in the `tests` subproject in the main source directory, which are then called from\nthe three different environments, exploded module, module jar, and jlink image.\n\nTo run all tests, we need to run the following commands:\n- `gradle :tests:test` (Exploded module)\n- `gradle :tests:run` (Module jar)\n- `gradle :tests:createImage` and then run `jlink_tests.bat` (jlink image)\n\nSome features and use cases have not been tested yet.\nFor example, an application that uses a mix of full modules,\nautomatic modules, and non-modular jars has not been tested yet.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxpipe-io%2Fmodulefs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxpipe-io%2Fmodulefs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxpipe-io%2Fmodulefs/lists"}