CLI wrapper and version management
When you install the Moderne CLI (via the curl/PowerShell installer, Homebrew, or Chocolatey) — what actually gets installed is a lightweight wrapper script called modw. The mod command you use is a symlink to this wrapper. Each time you run a mod command, the wrapper handles downloading the correct CLI distribution for your platform, locating a compatible Java runtime, and optimizing startup performance before launching the CLI itself.
Most users never need to think about this. But if you need to control which CLI version your team uses, pin a version for CI reproducibility, understand what network calls the CLI makes, or troubleshoot startup issues, this guide will explain how the wrapper works and how to configure it.
What is modw?
The standard installation methods place modw in ~/.moderne/cli/bin/ and add it to your PATH. When you run any mod command, the wrapper handles four things before launching the CLI:
- Ensures a distribution is installed — downloads the platform-specific CLI distribution (JAR + bundled JRE) if needed
- Finds a JDK — locates a compatible Java runtime
- Finds the CLI JAR — resolves which JAR to launch
- Optimizes startup — manages an ahead-of-time (AOT) compilation cache for faster startup
On first run after installation or an update, you may notice a brief delay while the wrapper downloads the distribution and builds the AOT cache. Subsequent runs reuse the cached artifacts and start quickly.
Controlling auto-updates
By default, the wrapper is configured with version=RELEASE in its properties file. This means every invocation checks Maven Central for the latest release version and downloads it if a newer version is available. For individual developers, this makes the most sense as you'd want to stay current automatically.
That being said, for CI/CD pipelines where reproducibility matters, enterprise environments with network restrictions or change-control policies, or air-gapped networks where Maven Central is not reachable, you'll want to pin to a specific version instead:
mod wrapper --global --version <version>
This writes the specified version to your global moderne-wrapper.properties file. The wrapper will use exactly that version and stop checking Maven Central for updates.
To re-enable auto-updates later:
mod wrapper --global --auto-update
This sets the version back to RELEASE.
You can also track the latest snapshot builds (for testing pre-release changes) with:
mod wrapper --global --auto-update-snapshot
This sets the version to LATEST, which resolves against Maven Central Snapshots.
Project wrapper
Similar to the Gradle wrapper, you can commit a wrapper into your project repository so that every developer and CI job uses a consistent CLI version.
To generate the wrapper files, run:
mod wrapper
This creates three files in your project:
modw— Unix wrapper scriptmodw.cmd— Windows wrapper scriptmoderne/wrapper/moderne-wrapper.properties— version configuration
Commit these files to your repository. Anyone who clones it can then run ./modw <command> without installing the CLI globally — on first run, the project wrapper downloads the pinned CLI version automatically.
To update the pinned version for the project:
mod wrapper --version <version>
How version resolution works
The wrapper looks for moderne-wrapper.properties in two locations, in order:
- Project-local:
moderne/wrapper/moderne-wrapper.properties(in the current working directory) - Global:
~/.moderne/cli/dist/moderne-wrapper.properties
A project-local properties file takes precedence. This allows teams to pin a specific CLI version in their repository while developers track RELEASE globally.
What gets downloaded and from where
The wrapper makes outbound network calls to two services:
| What | Source | When |
|---|---|---|
| CLI distribution (JAR + JRE) | Maven Central | On first run or version change |
| JDK (if no compatible Java is found) | Eclipse Adoptium | Only when no bundled JRE or system JDK is available |
You can control both of these with properties (see below).
Properties reference
The moderne-wrapper.properties file supports these properties:
| Property | Description | Default |
|---|---|---|
version | CLI version to use. RELEASE resolves the latest release from Maven Central. LATEST resolves the latest snapshot. Or pin a specific version like 4.x.x. | RELEASE |
distributionUrl | URL template for the distribution archive. Use ${version} and ${platform} as placeholders. | Maven Central |
distributionUsername | Username for basic authentication when downloading the distribution. | (none) |
distributionPassword | Password for basic authentication when downloading the distribution. | (none) |
distributionToken | Bearer token for authentication when downloading the distribution. Takes precedence over username/password if both are set. | (none) |
distributionSha256Sum | Expected SHA-256 of the downloaded archive. Verified if set. | (none) |
jdkUrl | URL to a JDK archive for auto-download. Set to skip to disable JDK auto-download entirely. | Adoptium API |
Air-gapped or restricted environments
If your network cannot reach Maven Central, you can host the CLI distribution on an internal mirror and point the wrapper to it:
version=4.x.x
distributionUrl=https://internal-mirror.example.com/moderne-cli-${platform}/${version}/moderne-cli-${platform}-${version}.sh
distributionSha256Sum=abc123...
jdkUrl=skip
Setting jdkUrl=skip disables the JDK auto-download, which is useful when you know a compatible JDK is already available on the system.
The example above uses the .sh extension, which applies to Linux and macOS distributions. Windows distributions use .zip instead.
Authenticated artifact repositories
If your internal mirror requires authentication (e.g., a private Artifactory or Nexus instance), you can configure credentials via properties or environment variables.
Using properties (for developer workstations):
version=4.x.x
distributionUrl=https://artifactory.corp.example.com/moderne-cli-${platform}/${version}/moderne-cli-${platform}-${version}.sh
distributionUsername=your-username
distributionPassword=your-password
jdkUrl=skip
Or with a bearer token:
distributionToken=your-token
You can set these properties via mod wrapper:
mod wrapper --global \
--distribution-url "https://artifactory.corp.example.com/moderne-cli-\${platform}/\${version}/moderne-cli-\${platform}-\${version}.sh" \
--distribution-username your-username \
--distribution-password
Omitting the value after --distribution-password will prompt you interactively, keeping the password out of your shell history.
Using environment variables (for CI/CD and first-time installs):
Environment variables take precedence over properties file values. This is especially important for first-time installs where no properties file exists yet.
| Variable | Description |
|---|---|
MODERNE_WRAPPER_DISTRIBUTION_USERNAME | Basic auth username |
MODERNE_WRAPPER_DISTRIBUTION_PASSWORD | Basic auth password |
MODERNE_WRAPPER_DISTRIBUTION_TOKEN | Bearer token (overrides user/pass) |
MODERNE_WRAPPER_DISTRIBUTION_URL | Override distribution URL |
MODERNE_WRAPPER_VERSION | Override CLI version |
Example first-time install with authentication:
export MODERNE_WRAPPER_DISTRIBUTION_URL="https://artifactory.corp.example.com/moderne-cli-\${platform}/\${version}/moderne-cli-\${platform}-\${version}.sh"
export MODERNE_WRAPPER_DISTRIBUTION_USERNAME="deploy-user"
export MODERNE_WRAPPER_DISTRIBUTION_PASSWORD="secret"
export MODERNE_WRAPPER_VERSION="RELEASE"
curl -fsSL https://app.moderne.io/cli | bash
When both a bearer token and username/password are configured, the bearer token takes precedence. Environment variables always take precedence over properties file values.
How the wrapper finds Java
The CLI requires Java 25+. The wrapper looks for a compatible runtime in this order:
MODERNE_JAVA_HOMEenvironment variable (used unconditionally if set)JAVA_HOMEenvironment variable (if version is 25+)javaonPATH(if version is 25+)- Well-known JDK locations (SDKMAN, macOS
/Library/Java/JavaVirtualMachines, IntelliJ/Gradle toolchains,/usr/lib/jvm, etc.) - Bundled JRE at
~/.moderne/cli/dist/jre/(included in platform distributions) - Auto-download from Eclipse Adoptium to
~/.moderne/cli/dist/jdk/
Most users never need to think about this — the platform distribution includes a bundled JRE (step 5), so no separate Java installation is required.
The macOS distribution bundles a JRE for Apple Silicon only. Intel Mac users will need to install a Java 25+ runtime separately (e.g., from Eclipse Adoptium) and ensure it is available via one of the locations above.
GraalVM distributions are not compatible with the CLI's AOT cache. The wrapper will skip GraalVM installations during JDK discovery. If you are supplying your own Java 25+ runtime, use a non-GraalVM distribution such as Eclipse Adoptium.
Environment variables
| Variable | Description |
|---|---|
MODERNE_JAVA_HOME | Override the JDK used to run the CLI |
MODERNE_JAR | Override the CLI JAR location (skips distribution download) |
MODERNE_OPTS | Additional JVM options passed to the CLI (e.g., -Xmx4g) |
MODERNE_CLI_HOME | Base CLI directory (default: ~/.moderne/cli) |
MODERNE_WRAPPER_DISTRIBUTION_USERNAME | Basic auth username for distribution downloads |
MODERNE_WRAPPER_DISTRIBUTION_PASSWORD | Basic auth password for distribution downloads |
MODERNE_WRAPPER_DISTRIBUTION_TOKEN | Bearer token for distribution downloads (overrides user/pass) |
MODERNE_WRAPPER_DISTRIBUTION_URL | Override distributionUrl without a properties file |
MODERNE_WRAPPER_VERSION | Override version without a properties file |
Directory layout
Everything lives under ~/.moderne/cli/ (or $MODERNE_CLI_HOME):
~/.moderne/
└── cli/
├── bin/ # on PATH — wrapper only
│ ├── mod -> modw # symlink
│ └── modw # wrapper script
├── dist/ # runtime assets
│ ├── moderne-wrapper.properties # global wrapper configuration
│ ├── version.txt # installed version stamp
│ ├── lib/
│ │ └── moderne-cli.jar # CLI fat JAR
│ ├── jre/ # bundled JRE (platform distribution)
│ ├── jdk/ # auto-downloaded JDK (when no bundled JRE)
│ ├── aot/
│ │ ├── mod.aot # AOT compilation cache
│ │ └── mod.aot.jar-stamp # cache invalidation stamp
│ └── classpath/ # extracted JARs for build plugins
└── ... # recipes, metrics, and other CLI config
Running the CLI without the wrapper
Some teams download the CLI JAR directly from Artifactory or another artifact repository and run it with java -jar. While this works, we recommend using the wrapper for everything except mass ingest. The wrapper manages an ahead-of-time (AOT) compilation cache that significantly improves CLI startup performance — running the JAR directly does not benefit from this optimization.
Migrating to the wrapper
If you're currently running the JAR directly, you don't need to change everything at once:
- Start with developer machines — have developers install via
curl https://app.moderne.io/cli | bashwhile CI continues using the JAR directly - Point the wrapper at your internal mirror — set
distributionUrlinmoderne-wrapper.propertiesto your Artifactory URL so the wrapper downloads from there instead of Maven Central - Adopt the project wrapper for CI — commit
modwto your repository and have CI run./modwinstead ofjava -jar. This ensures CI and developers use the same version
Your existing tooling for environment setup (e.g., scripts that configure Java versions, populate .moderne/moderne.yml, or set repository-specific environment variables) can continue to work alongside the wrapper.
Checking your configuration
To check whether auto-updates are enabled, inspect the wrapper properties:
cat ~/.moderne/cli/dist/moderne-wrapper.properties
If this file contains version=RELEASE, auto-updates are enabled. A specific version like version=4.x.x means you're pinned. If this file doesn't exist, you're likely running the CLI JAR directly without the wrapper.