Skip to content

feat: run modular jars as modules + full --module grammar - #2662

Draft
maxandersen wants to merge 6 commits into
jbangdev:mainfrom
maxandersen:module-main-class
Draft

maxandersen wants to merge 6 commits into
jbangdev:mainfrom
maxandersen:module-main-class

Conversation

@maxandersen

@maxandersen maxandersen commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Draft for review.

What

Makes JBang run modular JARs the way they were packaged, and gives --module the full module[/mainclass] grammar (java's own -m syntax, plus two small JBang extensions).

Why

A modular CLI whose main class lives only in module-info.class (with no Main-Class manifest entry) — e.g. com.netflix:com.netflix.tools.jfmt — could not be run sensibly:

  • java -jar fails outright (no manifest), and
  • running it on the classpath loses everything the module was built for: ServiceLoader providers, strong encapsulation, and the module version (jfmt reports dev on the classpath vs 0.8.2 as a module).

Behaviour

Execution mode now follows where the main class comes from:

main-class source mode
Main-Class manifest classpath
module descriptor only (no manifest) module (-m <module>)
jar scan classpath

--module forces module execution and accepts java's module[/mainclass] grammar, with two JBang extensions — an empty module part derives the module from the jar, and the main class may be a glob:

command runs
jbang run app.jar classpath, or module if main is only in the descriptor
jbang run -m com.acme.Main app.jar com.acme.Main on classpath
jbang run --module app.jar -m <jarModule>
jbang run --module=some.mod/com.acme.Main app.jar -m some.mod/com.acme.Main
jbang run --module=some.mod/com.acme.* app.jar search module for a matching main (prompts)
jbang run --module=/com.acme.Main app.jar derive module, run that class
jbang run --module --main com.acme.Main app.jar same as --module=/com.acme.Main

Semantics

  • A glob (or an ambiguous scan) is a request to choose, so it always prompts; running automatically requires an explicit main class.
  • Conflicting main via both --module=x/Y and --main Z is rejected.
  • No JBang-side "module not found" pre-check — the launched module name can legitimately differ from what ModuleFinder enumerates (automatic names, unbuilt jars, custom module-info), so this is left to the JVM. (See ponytail note in JarCmdGenerator.)

Tests / docs

  • TestModuleLaunch — executable spec (13 cases) for the grammar + anti-cases.
  • Extended TestJarMainDetection; updated TestModule/TestRun for the new defaults.
  • running.adoc — new "Classpath vs module execution" section with the grammar table.

Note

Builds cleanly on top of the jandex 3 upgrade in #2661 (needed to scan modern class files); no dependency between the diffs beyond JarCmdGenerator.

Modular jars (e.g. com.netflix:com.netflix.tools.jfmt) often declare
their main class in module-info.class instead of a Main-Class manifest
entry (some ship without any manifest, where 'java -jar' fails with
'Invalid or corrupt jarfile'). JBang now checks the module descriptor
before falling back to scanning the jar for main() candidates, so such
jars run directly without prompting the user to pick a main class.
When --module is used with a jar whose main class comes from the module
descriptor (or a scanned class), emit '-m <module>[/<main>]' so the JVM
runs it as a named module, instead of appending a bare class name to the
module path (which left the code in the unnamed module).
… match

- In --module mode with no explicit main, keep emitting a bare '-m <module>'
  and never scan (the JVM resolves the descriptor main). Test hardened to use
  a manifest-less jar (like real modular jars) and assert no '<module>/<main>'.
- A glob --main now works in module mode too: it bypasses the descriptor,
  scans classes, and emits '-m <module>/<chosen>'.
- A single (glob-matched) candidate now runs without an interactive prompt,
  making glob search usable non-interactively.
- adopt java's -m <module>[/<mainclass>] grammar for the --module value
- jbang extensions: empty module part (/Class) derives the module from the
  jar; the class part may be a glob to search for
- default (no flags): a manifest-less jar with a module-descriptor main-class
  now runs as a module (was classpath), matching how it was packaged
- single (glob-matched) main candidate auto-runs without an interactive prompt
- conflicting main (--module=x/Y and --main Z) is rejected
- no jbang-side unknown-module pre-check (launched name can validly differ from
  enumerable module names); JVM reports it at runtime

TestModuleLaunch adds an executable spec for the grammar + anti-cases.
- add 'Classpath vs module execution' section with the full
  --module[=module[/class-or-glob]] grammar table and mode-selection rules
- note the --module vs -m/--main distinction
- update main-class detection docs: single scanned/glob candidate now runs
  without prompting
@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • ai-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 4900e26a-5116-4672-b53e-2dd8d55bf70b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Reverts the single-candidate auto-select: a glob (or ambiguous scan) is a
request to choose, so it always prompts; running automatically requires an
explicit main class. Restores the candidate-list behaviour for a lone match
and updates the module glob tests + docs accordingly.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant