{"id":26629931,"url":"https://github.com/gradlex-org/extra-java-module-info","last_synced_at":"2025-04-07T15:05:32.172Z","repository":{"id":39180770,"uuid":"257902865","full_name":"gradlex-org/extra-java-module-info","owner":"gradlex-org","description":"A Gradle 6.8+ plugin to use legacy Java libraries as Java Modules in a modular Java project","archived":false,"fork":false,"pushed_at":"2025-03-31T07:38:43.000Z","size":771,"stargazers_count":113,"open_issues_count":8,"forks_count":7,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-03-31T08:22:18.880Z","etag":null,"topics":["gradle-plugin","java","java-modules","jpms"],"latest_commit_sha":null,"homepage":"","language":"Groovy","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gradlex-org.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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}},"created_at":"2020-04-22T13:01:27.000Z","updated_at":"2025-03-31T07:38:46.000Z","dependencies_parsed_at":"2023-10-02T08:15:09.816Z","dependency_job_id":"da9d6b91-a7ee-467b-aa36-d7599a75b260","html_url":"https://github.com/gradlex-org/extra-java-module-info","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gradlex-org%2Fextra-java-module-info","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gradlex-org%2Fextra-java-module-info/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gradlex-org%2Fextra-java-module-info/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gradlex-org%2Fextra-java-module-info/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gradlex-org","download_url":"https://codeload.github.com/gradlex-org/extra-java-module-info/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247675597,"owners_count":20977376,"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":["gradle-plugin","java","java-modules","jpms"],"created_at":"2025-03-24T13:16:16.642Z","updated_at":"2025-04-07T15:05:32.165Z","avatar_url":"https://github.com/gradlex-org.png","language":"Groovy","readme":"# Extra Java Module Info Gradle plugin\n\n[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fgradlex-org%2Fextra-java-module-info%2Fbadge%3Fref%3Dmain\u0026style=flat)](https://actions-badge.atrox.dev/gradlex-org/extra-java-module-info/goto?ref=main)\n[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v?label=Plugin%20Portal\u0026metadataUrl=https%3A%2F%2Fplugins.gradle.org%2Fm2%2Forg%2Fgradlex%2Fextra-java-module-info%2Forg.gradlex.extra-java-module-info.gradle.plugin%2Fmaven-metadata.xml)](https://plugins.gradle.org/plugin/org.gradlex.extra-java-module-info)\n\nA Gradle 6.8+ plugin to use legacy Java libraries as _Java Modules_ in a modular Java project.\n\nThis [GradleX](https://gradlex.org) plugin is maintained by me, [Jendrik Johannes](https://github.com/jjohannes).\nI offer consulting and training for Gradle and/or the Java Module System - please [reach out](mailto:jendrik.johannes@gmail.com) if you are interested.\nThere is also my [YouTube channel](https://www.youtube.com/playlist?list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) on Gradle topics.\n\nSpecial thanks goes to [Ihor Herasymenko](https://github.com/iherasymenko) who has been contributing many features and fixes to this plugin!\n\nIf you have a suggestion or a question, please [open an issue](https://github.com/gradlex-org/extra-java-module-info/issues/new).\n\nThere is a [CHANGELOG.md](CHANGELOG.md).\n\n# Java Modules with Gradle\n\nIf you plan to build Java Modules with Gradle, you should consider using these plugins on top of Gradle core:\n\n- [`id(\"org.gradlex.java-module-dependencies\")`](https://github.com/gradlex-org/java-module-dependencies)  \n  Avoid duplicated dependency definitions and get your Module Path under control\n- [`id(\"org.gradlex.java-module-testing\")`](https://github.com/gradlex-org/java-module-testing)  \n  Proper test setup for Java Modules\n- [`id(\"org.gradlex.extra-java-module-info\")`](https://github.com/gradlex-org/extra-java-module-info)  \n  Only if your (existing) project cannot avoid using non-module legacy Jars\n\n[Here is a sample](https://github.com/gradlex-org/java-module-testing/tree/main/samples/use-all-java-module-plugins)\nthat shows all plugins in combination.\n\n[In episodes 31, 32, 33 of Understanding Gradle](https://github.com/jjohannes/understanding-gradle) I explain what these plugins do and why they are needed.\n[\u003cimg src=\"https://onepiecesoftware.github.io/img/videos/31.png\" width=\"260\"\u003e](https://www.youtube.com/watch?v=X9u1taDwLSA\u0026list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE)\n[\u003cimg src=\"https://onepiecesoftware.github.io/img/videos/32.png\" width=\"260\"\u003e](https://www.youtube.com/watch?v=T9U0BOlVc-c\u0026list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE)\n[\u003cimg src=\"https://onepiecesoftware.github.io/img/videos/33.png\" width=\"260\"\u003e](https://www.youtube.com/watch?v=6rFEDcP8Noc\u0026list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE)\n\n[Full Java Module System Project Setup](https://github.com/jjohannes/gradle-project-setup-howto/tree/java_module_system) is a full-fledged Java Module System project setup using these plugins.  \n[\u003cimg src=\"https://onepiecesoftware.github.io/img/videos/15-3.png\" width=\"260\"\u003e]([https://www.youtube.com/watch?v=uRieSnovlVc\u0026list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE](https://www.youtube.com/watch?v=T9U0BOlVc-c\u0026list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE))\n\n# How to use this plugin\n\nThis plugin allows you to add module information to a Java library that does not have any.\nIf you do that, you can give it a proper _module name_ and Gradle can pick it up to put it on the _module path_ during compilation, testing and execution.\n\nThe plugin should be applied to **all subprojects** of your multi-project build.\nIt is recommended to use a [convention plugin](https://docs.gradle.org/current/samples/sample_convention_plugins.html#organizing_build_logic) for that.\n\n## Plugin dependency\n\nAdd this to the build file of your convention plugin's build\n(e.g. `gradle/plugins/build.gradle(.kts)` or `buildSrc/build.gradle(.kts)`).\n\n```\ndependencies {\n    implementation(\"org.gradlex:extra-java-module-info:1.12\")\n}\n```\n\n## Defining extra module information\nIn your convention plugin, apply the plugin and define the additional module info:\n\n```\nplugins {\n    ...\n    id(\"org.gradlex.extra-java-module-info\")\n}\n\n// add module information for all direct and transitive dependencies that are not modules\nextraJavaModuleInfo {\n    // failOnMissingModuleInfo = false\n    // failOnAutomaticModules = true\n    // skipLocalJars = true\n    module(\"commons-beanutils:commons-beanutils\", \"org.apache.commons.beanutils\") {\n        exports(\"org.apache.commons.beanutils\")\n        // or granuarly allowing access to a package by specific modules\n        // exports(\"org.apache.commons.beanutils\",\n        //         \"org.mycompany.server\", \"org.mycompany.client\")\n        // or simply export all packages\n        // exportAllPackages()\n        \n        requiresTransitive(\"org.apache.commons.logging\")\n        requires(\"java.sql\")\n        requires(\"java.desktop\")\n        \n        // closeModule()\n        // opens(\"org.apache.commons.beanutils\")\n        // or granuarly allowing runtime-only access to a package by specific modules\n        // opens(\"org.apache.commons.beanutils\",\n        //       \"org.mycompany.server\", \"org.mycompany.client\")\n        \n        // requiresTransitive(...)\n        // requiresStatic(...)\n        \n        // requireAllDefinedDependencies()\n    }\n    module(\"commons-cli:commons-cli\", \"org.apache.commons.cli\") {\n        exports(\"org.apache.commons.cli\")\n    }\n    module(\"commons-collections:commons-collections\", \"org.apache.commons.collections\")\n    automaticModule(\"commons-logging:commons-logging\", \"org.apache.commons.logging\")\n    \n    // when the Jar has a classifier - 'linux-x86_64' in this example:\n    module(\"io.netty:netty-transport-native-epoll|linux-x86_64\",\n           \"io.netty.transport.epoll.linux.x86_64\") \n    // when you somehow cannot address a Jar via coordinates, you may use the Jar name:\n    module(\"commons-logging-1.2.jar\", \"org.apache.commons.loggin\")\n}\n```\n\n## Dependencies in build files\n\nNow dependencies defined in your build files are all treated as modules if enough extra information was provided.\nFor example:\n\n```\ndependencies {\n    implementation(\"com.google.code.gson:gson:2.8.6\")           // real module\n    implementation(\"net.bytebuddy:byte-buddy:1.10.9\")           // real module with multi-release jar\n    implementation(\"org.apache.commons:commons-lang3:3.10\")     // automatic module\n    implementation(\"commons-beanutils:commons-beanutils:1.9.4\") // plain library (also brings in other libraries transitively)\n    implementation(\"commons-cli:commons-cli:1.4\")               // plain library        \n}\n```\n\nSample uses Gradle's Kotlin DSL (`build.gradle.kts` file). The Groovy DSL syntax is similar.\n\n# FAQ\n\n## How do I (de)activate the plugin functionality for a certain classpath?\n\nThe plugin activates Jar transformation functionality individually for each\n[resolvable configuration](https://docs.gradle.org/current/userguide/declaring_configurations.html#sec:configuration-flags-roles)\n(.e.g `compileClasspath`, `runtimeClasspath`, `testRuntimeClasspath`).\nWhen any of these configurations are used, for example by the `compileJava` task, the transformed Jars will be used\ninstead of the original ones. By default, the plugin activates for all configurations that are linked to a _source set_,\nwhich are: `\u003csourcSet\u003eCompileClasspath`, `\u003csourcSet\u003eRuntimeClasspath` and `\u003csourceSet\u003eAnnotationProcessor`.\n\nYou can customize this as follows:\n\n- Completely deactivate the plugin for a source set:\u003cbr/\u003e\n  `extraJavaModuleInfo { deactivate(sourceSets.test) }`\n- Deactivate the plugin for a selected configuration:\u003cbr/\u003e\n  `extraJavaModuleInfo { deactivate(configurations.annotationProcessor) }`\n- Activate the plugin for a custom configuration that is not linked to a source set:\u003cbr/\u003e\n  `extraJavaModuleInfo { activate(configurations[\"myCustomPath\"]) }`\n\n## How do I deactivate the plugin functionality for my own Jars?\n\nA major use case of the plugin is to transform Jars from 3rd party repositories that you do not control.\nBy default, however, the plugin looks at all Jars on the module paths – including the Jars Gradle builds from you own modules.\nThis is working well in most cases. The jars are analyzed and the plugin detects that they are in fact modules and does not modify them.\nYou can still optimize the plugin execution to completely skip analysis of locally-built Jars by setting `skipLocalJars = true`.\n\n## How do I add `provides ... with ...` declarations to the `module-info.class` descriptor?\n\nThe plugin will automatically retrofit all the available `META-INF/services/*` descriptors into `module-info.class` for you. The `META-INF/services/*` descriptors will be preserved so that a transformed JAR will continue to work if it is placed on the classpath.\n\nThe plugin also allows you to ignore some unwanted services from being automatically converted into `provides .. with ...` declarations. \n\n```\nextraJavaModuleInfo {               \n    module(\"groovy-all-2.4.15.jar\", \"groovy.all\", \"2.4.15\") {\n       requiresTransitive(\"java.scripting\")\n       requires(\"java.logging\")\n       requires(\"java.desktop\")\n       ignoreServiceProvider(\"org.codehaus.groovy.runtime.ExtensionModule\")\n       ignoreServiceProvider(\"org.codehaus.groovy.plugins.Runners\")\n       ignoreServiceProvider(\"org.codehaus.groovy.source.Extensions\")\n    }\n}\n```\n\nYou can also be more granular and ignore specific implementations while leaving the remaining active.\n\n```\nextraJavaModuleInfo {\n    module(\"org.liquibase:liquibase-core\", \"liquibase.core\") {\n        ...\n        ignoreServiceProvider(\n            \"liquibase.change.Change\", // the provider\n            \"liquibase.change.core.LoadDataChange\", \"liquibase.change.core.LoadUpdateDataChange\" // Ignored implementations\n        )\n    }\n}\n```\n\n## Should I use real modules or automatic modules?\n\nOnly if you use _real_ modules (Jars with `module-info.class`) everywhere you can use all features of the Java Module System\n(see e.g. [#38](https://github.com/gradlex-org/extra-java-module-info/issues/38) for why it may be problematic to depend on an automatic module).\nStill, using automatic modules is more convenient if you need to work with a lot of legacy libraries, because you do not need to define `exports` and `requires` directives.\nAlternatively though, this plugin offers a way to define a _real_ module, without defining all of those directives explicitly:\n\n```\nextraJavaModuleInfo {\n    module(\"org.apache.httpcomponents:httpclient\", \"org.apache.httpcomponents.httpclient\") {\n        exportAllPackages() // Adds an `exports` for each package found in the Jar\n        requireAllDefinedDependencies() // Adds `requires (transitive|static)` directives based on dependencies defined in the component's metadata\n    }\n}\n```\n\n## How can I avoid that the same Jar is transformed multiple times when using requireAllDefinedDependencies?\n\nWhen using the `requireAllDefinedDependencies` option, all metadata of the dependencies on your classpath is input to the Jar transformation.\nIn a multi-project however, each subproject typically has different classpaths and not all metadata is available everywhere.\nThis leads to a situation, where Gradle's transformation system does not know if transforming the same Jar will lead to the same result.\nThen, the same Jar is transformed many times. This is not necessary a problem, as the results of the transforms are cached\nand do not run on every build invocation. However, the effect of this is still visible:\nfor example when you import the project in IntelliJ IDEA.\nYou see the same dependency many times in the _External Libraries_ list and IntelliJ is doing additional indexing work.\n\nTo circumvent this, you need to construct a common classpath – as a _resolvable configuration_ – that the transform can use.\nThis needs to be done in all subprojects. You use the `versionsProvidingConfiguration` to tell the plugin about the commons classpath.\n\n```\nextraJavaModuleInfo {\n    versionsProvidingConfiguration = \"mainRuntimeClasspath\"\n}\n```\n\nTo create such a common classpath, some setup work is needed.\nAnd it depends on your overall project structure if and how to do that.\nHere is an example setup you may use:\n\n```\nval consistentResolutionAttribute = Attribute.of(\"consistent-resolution\", String::class.java)\n\n// Define an Outgoing Variant (aka Consumable Configuration) that knows about all dependencies\nconfigurations.create(\"allDependencies\") {\n    isCanBeConsumed = true\n    isCanBeResolved = false\n    sourceSets.all {\n        extendsFrom(\n            configurations[this.implementationConfigurationName],\n            configurations[this.compileOnlyConfigurationName],\n            configurations[this.runtimeOnlyConfigurationName],\n            configurations[this.annotationProcessorConfigurationName]\n        )\n    }\n    attributes { attribute(consistentResolutionAttribute, \"global\") }\n}\n\n// Define a \"global claspath\" (as Resolvable Configuration)\nval mainRuntimeClasspath = configurations.create(\"mainRuntimeClasspath\") {\n    isCanBeConsumed = false\n    isCanBeResolved = true\n    attributes.attribute(consistentResolutionAttribute, \"global\")\n}\n\n// Add a dependency to the 'main' project(s) (:app ins this example) that transitively \n// depend on all subprojects to create a depenedency graph wih \"everything\"\ndependencies { mainRuntimeClasspath(project(\":app\")) }\n\n// Use the global classpath for consisten resolution (optional)\nconfigurations.runtimeClasspath {\n    shouldResolveConsistentlyWith(mainRuntimeClasspath)\n}\n```\n\n## I have many automatic modules in my project. How can I convert them into proper modules and control what they export or require?\n\nThe plugin provides a set of `\u003csourceSet\u003emoduleDescriptorRecommendations` tasks that generate the real module declarations utilizing [jdeps](https://docs.oracle.com/en/java/javase/11/tools/jdeps.html) and dependency metadata.\n\nThis task generates module info spec for the JARs that do not contain the proper `module-info.class` descriptors.\n\nNOTE: This functionality requires Gradle to be run with Java 11+ and failing on missing module information should be disabled via `failOnMissingModuleInfo = false`.\n\n## How can I ensure there are no automatic modules in my dependency graph?\n\nIf your goal is to fully modularize your application, you should enable the following configuration setting, which is disabled by default.\n\n```\nextraJavaModuleInfo {\n    failOnAutomaticModules = true\n}\n```\n\nWith this setting enabled, the build will fail unless you define a module override for every automatic module that appears in your dependency tree, as shown below.\n\n```\ndependencies {\n    implementation(\"org.yaml:snakeyaml:1.33\")\n}             \nextraJavaModuleInfo {\n    failOnAutomaticModules = true\n    module(\"org.yaml:snakeyaml\", \"org.yaml.snakeyaml\") {\n        closeModule()\n        exports(\"org.yaml.snakeyaml\")\n    }\n}\n```\n\n## What do I do in a 'split package' situation?\n\nThe Java Module System does not allow the same package to be used in more than one _module_.\nThis is an issue with legacy libraries, where it was common practice to use the same package in multiple Jars.\nThis plugin offers the option to merge multiple Jars into one in such situations:\n\n```\n extraJavaModuleInfo {\n    module(\"org.apache.zookeeper:zookeeper\", \"org.apache.zookeeper\") {\n        mergeJar(\"org.apache.zookeeper:zookeeper-jute\")\n        \n        // ...\n    }\n    automaticModule(\"org.slf4j:slf4j-api\", \"org.slf4j\") {\n        mergeJar(\"org.slf4j:slf4j-ext\")\n    }\n}\n```\n\nNote: The merged Jar will include the *first* appearance of duplicated files (like the `MANIFEST.MF`).\n\nIn some cases, it may also be sufficient to remove appearances of the problematic package completely from some of the Jars.\nThis can be the case if classes are in fact duplicated, or if classes are not used.\nFor this, you can utilise the `removePackage` functionality:\n\n```\nextraJavaModuleInfo {\n    module(\"xerces:xercesImpl\", \"xerces\") {\n        removePackage(\"org.w3c.dom.html\")\n        // ...\n    }\n}\n```\n\n## How can I fix a library with a broken `module-info.class`?\n\nTo fix a library with a broken `module-info.class`, you can override the modular descriptor in the same way it is done with non-modular JARs.\nHowever, you need to specify `patchRealModule()` to overwrite the existing `module-info.class`.\nYou can also use `preserveExisting()`, if the exiting `module-info.class` is working in general, but misses entries.\n\n```\nextraJavaModuleInfo {                \n    module(\"org.apache.tomcat.embed:tomcat-embed-core\", \"org.apache.tomcat.embed.core\") {\n        patchRealModule()   // overwrite existing module-info.class\n        preserveExisting()  // extend existing module-info.class \n        requires(\"java.desktop\")\n        requires(\"java.instrument\")\n        ...\n    }\n}    \n```\n\nThis opt-in behavior is designed to prevent over-patching real modules, especially during version upgrades. For example, when a newer version of a library already contains the proper `module-info.class`, the extra module info overrides should be removed.\n\n## Can't things just work™ without all that configuration?\n\nIf you use legacy libraries and want to use the Java Module System with all its features, you should patch all Jars to include a `module-info`.\nHowever, if you get started and just want things to be put on the Module Path, you can set the following option:\n\n```\nextraJavaModuleInfo {\n    deriveAutomaticModuleNamesFromFileNames = true\n}\n```\n\nNow, also Jars that do not have a `module-info.class` and no `Automatic-Module-Name` entry will automatically be processed to get an `Automatic-Module-Name` based on the Jar file name.\nThis feature is helpful if you start to migrate an existing project to the Module Path.\nThe pivotal feature of this plugin though, is to add a complete `module-info.class` to all Jars using the `module(...)` patch option for each legacy Jar individually.\n\n# Disclaimer\n\nGradle and the Gradle logo are trademarks of Gradle, Inc.\nThe GradleX project is not endorsed by, affiliated with, or associated with Gradle or Gradle, Inc. in any way.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgradlex-org%2Fextra-java-module-info","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgradlex-org%2Fextra-java-module-info","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgradlex-org%2Fextra-java-module-info/lists"}