name: guicedee-jpms-shade
description: "Convert an automatic-module (or non-modular) JAR dependency into a GuicedEE shaded JPMS service module so the GuicedEE/jlink pipeline stays clean. Use when a GuicedEE module requires an automatic module (a JAR with only an Automatic-Module-Name or none), when jlink/jpms complains about automatic modules on the module path, or when adding a new com.guicedee.modules.services shade under services/Libraries. Covers the maven-shade + moditect module-info pattern, version + BOM registration (Versioner, StandaloneBOM, guicedee-bom), dev-suite build registration, and rewiring the consuming module."
GuicedEE JPMS Shade
Turn an automatic-module dependency into a proper, named JPMS module published as a
com.guicedee.modules.services artifact, then make the consuming GuicedEE module require the
shaded module instead of the automatic one. This yields a clean jlink image (no automatic
modules on the module path).
When This Applies
- A GuicedEE module
requires <some.automatic.module>that resolves to a plain JAR. jlink/jpmsrejects or warns about automatic modules.- Goal: a fully-named module graph for a reliable runtime image.
Inputs To Gather First
- The upstream coordinates and resolved version (e.g.
com.graphql-java:graphql-java:26.0). Resolve the EXACT version the parent already pulls (check the dependency's own POM, the BOM that imports it, ormvn dependency:tree). Pin to that version — do not guess. - The module name the JAR advertises. Read it from the JAR manifest
Automatic-Module-Name; if absent, derive a stable name from the root package. Seereferences/inspect-jar.mdfor ready-to-run commands to read the manifest and enumerate packages (needed for accurateexports). - The dependency's own external module dependencies (its compile deps that are NOT already
bundled/relocated inside the JAR). Each becomes a
requiresin the module-info. Annotation-only deps (jspecify, jsr305, checker, error_prone) becomerequires static.
Many libraries already relocate their transitives internally (e.g. graphql-java bundles ANTLR and Guava under
graphql.*). Inspect the package list — only NON-relocated, externally-visible dependencies need arequires.
Workflow
1. Create the shade module
Create GuicedEE/services/Libraries/<artifactId>/ (mirror the json / kafka-client modules):
pom.xml— parentcom.guicedee:parent, groupIdcom.guicedee.modules.services, the shadeartifactSetpinned to the single upstream artifact, and the standard plugin chain (shade → antrun → moditect → flatten → copy-rename → javadoc). Exclude transitives that you re-add as their own proper modules.src/moditect/module-info.java— the named module descriptor.
Use the exact templates in references/templates.md.
2. Write the moditect module-info
module <module-name> { ... }using the manifest's Automatic-Module-Name.exportsevery public package found in the JAR EXCEPT internally-relocated/shaded packages (e.g.*.com.google.*,*.org.antlr.*).requires transitivefor deps whose types appear in the exported API (e.g.org.reactivestreams).requires staticfor annotation-only deps.- Add
provides ... with ...for anyMETA-INF/servicesthe JAR ships (the SPI needs explicitprovidesin the descriptor, even though the shade keeps the service files).
3. Register versions and BOMs
GuicedEE/bom/Versioner/pom.xml— add<...version>properties for the upstream artifact(s).GuicedEE/bom/StandaloneBOM/pom.xml—dependencyManagementfor the UPSTREAM artifacts (so the shade module'spom.xmland its exclusions resolve versions).GuicedEE/bom/pom.xml(guicedee-bom) —dependencyManagementfor the newcom.guicedee.modules.services:<artifactId>at${guicedee.version}(so consumers omit versions).
4. Register in the dev-suite build (the "workflows")
- Root
DevSuite/pom.xml→servicesprofile<modules>list. This is the module list the dev-suite GitHub workflows build/deploy; the reusable workflows inGuicedEE/workflowsare generic templates and do NOT enumerate modules. Each shade also ships as its own GitHub repo that calls the reusableProjects Builderworkflow — mention this to the user, but it is created outside DevSuite. - Optionally document the mapping table in
GuicedEE/services/services.md.
5. Rewire the consuming module
In the GuicedEE module that used the automatic module:
- Exclude the upstream automatic artifact(s) from whatever transitively brought them in
(e.g. exclude
com.graphql-java:graphql-javafromio.vertx:vertx-web-graphql). - Add a dependency on the new
com.guicedee.modules.services:<artifactId>. - The consumer's
module-info.javausually needs no change: the shaded module keeps the same module name, sorequires <module-name>already resolves — now to a real named module.
6. Validate
Install the updated BOMs first (they are SNAPSHOTs the shade module imports), then build:
mvn -B -N install # in Versioner, then StandaloneBOM, then GuicedEE/bom
mvn -B install -DskipTests -Dmaven.javadoc.skip=true # in each new shade module
jar --describe-module --file target/<artifactId>-<ver>.jar # confirm requires/exports
mvn -B -o clean compile # in the consumer module — must compile on the module path
Confirm jar --describe-module shows the expected named module with correct requires/exports,
and that the consumer compiles with [debug release N module-path].
Pitfalls
- Build env (Windows): the repo
mvnwwrapper may be broken; a system Maven 4 (C:\Software\maven4) +JAVA_HOME=c:\software\jdk\25works. Chain PowerShell commands with;. - Relocated deps need explicit
requiresfor JDK modules. When a library internally relocates its dependencies (e.g. graphql-java bundles Guava undergraphql.com.google.*and ANTLR undergraphql.org.antlr.*), those classes still use JDK APIs such asjava.util.logging. As an automatic module the jar implicitly read every module, so it worked; as a named module it only reads what itrequires. Missing ones surface at RUNTIME, not compile time, asIllegalAccessError: class X (in module M) cannot access class Y (in module java.logging) because module M does not read module java.logging. Add the JDK module (e.g.requires java.logging;). Compile +jar --describe-modulewill NOT catch this — only running the code does. Always run the consumer's integration tests after a shade. - Missing version errors in the shade module mean the updated StandaloneBOM was not installed yet.
- Split packages: never let the shaded module and a separately-required module both export the same package — exclude the transitive from one side.
- Don't export relocated internals — they are implementation detail and break encapsulation.
References
references/templates.md— copy-pastepom.xmlandmodule-info.javatemplates + BOM snippets.references/inspect-jar.md— PowerShell snippets to read the manifest module name and package list.references/worked-example.md— the graphql-java + java-dataloader case, end to end.