deptool is a command-line utility that prepares a custom Nexus repository for developing Jmix-based
projects in an isolated environment (no internet access). It:
- resolves the full set of dependencies for a Jmix framework version (Java + npm), including commercial add-ons;
- resolves the dependencies of any single library;
- exports the resolved dependencies as a portable Maven repository (Java) and a folder of
.tgzarchives (npm); - uploads the exported artifacts into a custom Nexus repository.
Same CLI and same commands as v1/v2 — the internal implementation is reworked for simplicity, durability across framework versions, and correct npm version coverage (see Architecture details).
resolve-jmix resolve-lib resolve-npm export export-npm upload upload-npm
A JDK must be installed on the machine, and the required version depends on the target Jmix line. The
resolution build models a real Jmix app of that line, so the host JDK must satisfy everything that build needs
— its Gradle distribution, the io.jmix Gradle plugin, and the framework's own Java baseline: Jmix 3.x
requires JDK 21, 2.x runs on JDK 17 or 21, and 1.x needs JDK ≤ 17 (its Gradle 7.6 can't run on JDK
21). When in doubt use JDK 21 for 2.x/3.x. Node.js is not required — npm export is pure Java, and
resolve-npm lets the Vaadin plugin download Node automatically.
The tool distribution (zip archive) is produced by the build (see Building) — extract it and
use bin/deptool (Linux/macOS) or bin/deptool.bat (Windows) to run commands.
Distribution structure:
deptool
bin/ CLI executables (deptool, deptool.bat)
lib/ the deptool jar + its Java dependencies
Unlike v1/v2 there are no resolver/ or npm-resolver/ project directories: the resolution project
templates and the Gradle wrapper are embedded inside the deptool jar and generated at runtime.
Run via the launcher in the distribution: bin/deptool (Linux/macOS) or bin/deptool.bat (Windows).
Examples below use deptool for brevity.
# 1. Resolve into a local Gradle cache (Java) and a package-lock.json (npm)
deptool resolve-jmix --jmix-version 2.8.0
deptool resolve-npm --jmix-version 2.8.0
# 2. Export to portable, uploadable folders
deptool export # -> ../export (Maven repo layout)
deptool export-npm # -> ../export-npm (tgz archives)
# 3. Upload to your Nexus
deptool upload --nexus-url http://localhost:8081 --nexus-repository jmix --nexus-username admin --nexus-password admin --artifacts-dir ../export
deptool upload-npm --nexus-url http://localhost:8081 --nexus-repository jmix-npm --nexus-username admin --nexus-password admin --artifacts-dir ../export-npm
Paths in defaults are relative to the working directory (typically
deptool/bin):../gradle-home,../work,../export,../export-npm,../npm-work. Override any of them with the options below.Accumulation:
resolve-*commands add to the same--gradle-user-home. Resolving several Jmix versions accumulates all of them; clean../gradle-homefirst if you want a single version only.
These apply to resolve-jmix, resolve-npm, and resolve-lib:
| Option | Default | Description |
|---|---|---|
--jmix-plugin-version |
= --jmix-version |
Jmix Gradle plugin version, if it differs from the framework version. |
--gradle-user-home |
../gradle-home |
Directory Gradle resolves into (what export later reads). |
--workspace-dir |
../work |
Where the throw-away resolution project is generated (<dir>/project). |
--gradle-version |
per Jmix line¹ | Gradle distribution the generated wrapper downloads and runs. |
--gradle-jvmargs |
-Xmx2g -XX:MaxMetaspaceSize=512m |
JVM args for the Gradle daemon. |
--jmix-license-key |
– | License key user-password; adds the premium repo for commercial artifacts. |
--public-repository |
https://global.repo.jmix.io/repository/public |
Repository for public artifacts. |
--premium-repository |
https://global.repo.jmix.io/repository/premium |
Repository for premium artifacts. |
--repository |
– | Extra Maven repo, repeatable. Format <url> or <url>|<user>|<pass>. |
¹ Resolved from templates/gradle-versions.properties (1.0→7.6.4, 2.0→8.14.4, 3.0→9.5.1); --gradle-version overrides it.
Resolves the full Java dependency set for a Jmix version (every module in dependencies-<version>.xml,
each in isolation plus all together) into --gradle-user-home. If no descriptor for the requested version is
bundled, the command fails rather than falling back to a generic list — a wrong-but-successful resolve
would silently ship an incorrect dependency set (see Supporting a new framework version).
| Option | Required | Default | Description |
|---|---|---|---|
--jmix-version |
✓ | – | Jmix framework version, e.g. 2.8.0. |
--resolve-commercial-addons |
off | Also resolve commercial add-ons (requires --jmix-license-key). |
|
--commercial-subscription-plan |
bpm |
enterprise or bpm — which commercial modules to include. Only with --resolve-commercial-addons. |
|
--no-sources |
off | Skip downloading -sources jars — much faster. Sources are included by default. |
|
| (+ common resolve options above) |
# Open-source only
deptool resolve-jmix --jmix-version 2.8.0
# Including commercial add-ons (enterprise plan)
deptool resolve-jmix --jmix-version 2.8.0 \
--resolve-commercial-addons --commercial-subscription-plan enterprise \
--jmix-license-key 1234567-abcdef
# Through a corporate mirror, custom Gradle, custom cache location
deptool resolve-jmix --jmix-version 2.8.0 \
--public-repository https://nexus.corp/repository/jmix-public \
--repository "https://nexus.corp/repository/extra|ci|secret" \
--gradle-version 8.14.4 --gradle-user-home /data/jmix-cache
Resolves one dependency and its transitive closure (+ sources).
| Option | Required | Default | Description |
|---|---|---|---|
<dependency> (positional) |
✓ | – | Maven coordinates group:artifact:version. |
--jmix-version |
– | If given, the Jmix BOM of that version is applied (use for a library that depends on Jmix modules, or to also collect the Jmix-aligned versions). If omitted, the library is resolved standalone, with no Jmix BOM. | |
--no-sources |
off | Skip downloading -sources jars — much faster. Sources are included by default. |
|
| (+ common resolve options above) |
# Plain library, no Jmix in the picture
deptool resolve-lib com.google.guava:guava:33.0.0-jre
# A Jmix-based add-on (its version-less Jmix deps need the BOM)
deptool resolve-lib com.example:my-jmix-addon:1.0.0 --jmix-version 2.8.0
Runs Vaadin's vaadinBuildFrontend and writes two lockfiles into ../npm-work/ (see
npm resolution): package-lock.json (the project's resolved set — Jmix-specific packages)
and dev-bundle-package-lock.json (extracted from the resolved vaadin-dev-bundle jar — the framework's
frozen versions). export-npm unions both. Not supported for Jmix 1.x (no Vaadin frontend).
| Option | Required | Default | Description |
|---|---|---|---|
--jmix-version |
✓ | – | Jmix framework version (must be 2.x+). |
--package-lock-output |
../npm-work/package-lock.json |
Where the project lockfile is written; the dev-bundle lock is written next to it. | |
--resolve-commercial-addons |
off | Include commercial add-ons (requires --jmix-license-key). |
|
--commercial-subscription-plan |
bpm |
enterprise or bpm. Only with --resolve-commercial-addons. |
|
| (+ common resolve options above) |
deptool resolve-npm --jmix-version 2.8.0
deptool resolve-npm --jmix-version 2.8.0 --resolve-commercial-addons --jmix-license-key 1234567-abcdef
Copies resolved .jar / .pom / .module files from the Gradle cache into a portable Maven repository
directory.
| Option | Default | Description |
|---|---|---|
--gradle-user-home |
../gradle-home |
Cache to export from (the one the resolve-* commands filled). |
--target-dir |
../export |
Output directory (Maven layout). |
--report-file |
– | Optional file listing the exported group:artifact:versions — one line per module version, sorted, de-duplicated. Includes pom-only modules (e.g. io.jmix.bom), not just those shipping a jar. |
deptool export
deptool export --gradle-user-home /data/jmix-cache --target-dir /data/export \
--report-file /data/jmix-2.8.0-artifacts.txt
Reads the resolved lockfile(s) and downloads each tarball — every resolved version plus every
exact-pin variant — directly from the registry (pure Java, no Node.js). Multiple lockfiles are
unioned; by default it reads both files resolve-npm wrote (package-lock.json +
dev-bundle-package-lock.json). See npm resolution.
| Option | Default | Description |
|---|---|---|
--package-lock-file |
../npm-work/{package-lock,dev-bundle-package-lock}.json |
Lockfile to mirror; repeatable. Defaults to both lockfiles resolve-npm produced. |
--target-dir |
../export-npm |
Output directory of .tgz archives (+ the lockfile copies). |
--npm-registry |
https://registry.npmjs.org |
Registry used to look up tarballs for variant versions. |
--report-file |
– | Optional file listing mirrored (resolved + variant) versions. |
deptool export-npm
deptool export-npm --package-lock-file ./a/package-lock.json --package-lock-file ./b/package-lock.json
Uploads the exported artifacts to a Nexus repository (Maven hosted for upload, npm hosted for
upload-npm). Already-present artifacts are skipped.
| Option | Required | Description |
|---|---|---|
--nexus-url |
✓ | Nexus base URL, e.g. http://localhost:8081. |
--nexus-repository |
✓ | Target repository name. |
--nexus-username |
✓ | Nexus user. |
--nexus-password |
✓ | Nexus password. |
--artifacts-dir |
✓ | Directory of exported artifacts (../export for upload, ../export-npm for upload-npm). |
deptool upload --nexus-url http://localhost:8081 --nexus-repository jmix \
--nexus-username admin --nexus-password admin --artifacts-dir ../export
deptool upload-npm --nexus-url http://localhost:8081 --nexus-repository jmix-npm \
--nexus-username admin --nexus-password admin --artifacts-dir ../export-npm
While creating a new Jmix project in Jmix Studio, add the custom Nexus repository.
After the project is created, add the following lines to the beginning of the settings.gradle, using the
correct Nexus repository URL:
pluginManagement {
resolutionStrategy {
eachPlugin {
if (requested.id.id == 'io.jmix') {
useModule("io.jmix.gradle:jmix-gradle-plugin:${requested.version}")
}
if (requested.id.id == 'org.jetbrains.gradle.plugin.idea-ext') {
useModule("gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext:${requested.version}")
}
}
}
repositories {
maven {
allowInsecureProtocol true
url 'http://localhost:8081/repository/jmix/'
credentials {
username(rootProject.hasProperty('repoUser') ? rootProject['repoUser'] : 'admin')
password(rootProject.hasProperty('repoPass') ? rootProject['repoPass'] : 'admin')
}
}
}
}These lines are necessary for resolving the Jmix Gradle plugin.
allowInsecureProtocol true is required if your Nexus repository uses the http protocol. Add this
instruction to the maven repository configuration in the build.gradle file as well.
Remove the mavenCentral() instruction from the repositories section of the build.gradle file.
Copy the previously saved package-lock.json file (produced by resolve-npm) to the root of your project.
To use a custom npm registry, create a .npmrc file at the root of your project and add the following line:
registry=http://localhost:8081/repository/jmix-npm/
Additional properties (authentication, etc.) are described in the npm documentation: https://docs.npmjs.com/cli/v10/configuring-npm/npmrc
Download Sonatype Nexus OSS.
Unzip the archive.
Go to the bin directory and run Nexus using the command:
On Linux:
./nexus runOn Windows:
nexus.exe /runOpen the URL in the browser: http://localhost:8081
Click the Sign in button and change the default admin password.
Go to the Server administration and configuration section and click the Repositories button.
Click the Create repository button and select the maven2 (hosted) repository type.
Fill the Name field (e.g. jmix) and select the Version policy: Mixed.
Create the NPM repository the same way — use the npm (hosted) repository type and fill the Name
(e.g. jmix-npm).
deptool resolves dependencies by generating a throw-away Gradle project that mirrors a real Jmix application, running it via its own Gradle wrapper (as a subprocess), and then copying the resolved artifacts out of the Gradle cache. The high-level flow is unchanged from v1/v2 — pass a Jmix version or a library coordinate, let Gradle download everything, copy it out, upload it — but the internals are reworked.
| Area | v1/v2 | v3 |
|---|---|---|
| Resolution projects | Two shipped stub projects (resolver/ for Java + npm-resolver/ for npm) with frozen build.gradle |
One unified project generated at runtime (Java + Vaadin, like a real app) from versioned templates packaged as resources |
| Build logic | Custom Gradle plugins in buildSrc + custom tasks + inject.gradle init script |
A resolveAll task written directly into the generated build script — no buildSrc, no plugin jar, no init script |
resolve-jmix |
~2N+1 separate Gradle builds (per-module loop) |
One Gradle build with one configuration per module (iso_<n>) + all-modules + noBom |
| Dependency input | Two mechanisms (--dependency task option for Java, -PexternalDependencies for npm) |
One mechanism: -PjmixModules project property for both |
export-npm |
npm install node-tgz-downloader + npx download-tgz (re-resolves peerDependencies → wrong versions) |
Pure Java: parse the lockfile, collect resolved + exact-pin variant versions, download tarballs from the registry. No Node.js. |
| Additional npm deps | Hand-maintained additional/npm/<version>/package-lock.json to recover missed versions |
Removed — the dev-bundle lock + variant collection cover them automatically |
| Gradle execution | Embedded Gradle Tooling API (one static client version) | Generated Gradle wrapper run as a subprocess (--no-daemon) — the daemon Gradle is fully decoupled from the tool |
| Gradle version | Tooling API default / --gradle-version |
Per-checkpoint (gradle-versions.properties): 1.0→7.6.4, 2.0→8.14.4, 3.0→9.5.1; --gradle-version overrides |
| Distribution | deptool + resolver + npm-resolver | Just deptool (templates + Gradle wrapper embedded in the jar) |
deptool (single Gradle module)
src/main/java/io/jmix/dependency/cli/
command/ CLI commands (JCommander) — one class per command + shared defaults
gradle/ runs the generated wrapper as a subprocess; builds the shared -P argument list
workspace/ generates the resolution project + wrapper from templates; checkpoint selection
npm/ lockfile parsing, variant collection, registry packument lookup, tarball download + verify
dependency/ reads dependencies-*.xml; Maven coordinates; commercial subscription filtering
upload/ Nexus upload client + models
version/ Jmix version parsing / comparison
src/main/resources/
jmix-dependencies/dependencies-*.xml per-version module lists (JVM + NPM scoped)
templates/
settings.gradle
gradle-versions.properties recommended Gradle distribution per Jmix line
wrapper/ bundled Gradle wrapper (jar + gradlew scripts) written into each workspace
project/build-<x.y>.gradle project shape for the Jmix line starting at x.y
project/build-plain.gradle plain library resolution, no Jmix (resolve-lib without a version)
project/resolve-support.gradle shared resolution mechanism (common repos, module deps, iso configs, resolveAll)
project/frontend-index.html stub frontend entry for the Vaadin build
Package descriptions are intentionally high-level — the purpose of each package, not a class-by-class list.
A real Jmix app is a single project that carries both its Java and (via the Vaadin plugin) its npm
dependencies. deptool therefore generates one unified project rather than separate Java/NPM resolvers. The
same generated project serves every command — resolve-jmix/resolve-lib run its resolveAll task,
resolve-npm runs vaadinBuildFrontend — so Java resolution sees the same graph (Vaadin plugin applied)
it would in a real build. The set of modules added differs only by the scope filter in dependencies-*.xml
(JVM vs NPM), passed per command as -PjmixModules.
Shape vs mechanism. Each build-<checkpoint>.gradle describes only the version-specific shape — which
plugins, the jmix{}/vaadin{} blocks, the version-specific repositories (mavenCentral(),
gradlePluginPortal(), and any era repo such as the Shibboleth repo for jmix-saml), and any excludes/extra
plugin markers. The version-agnostic mechanism — the common repositories (Jmix public/premium +
caller-supplied extraRepositories), module dependencies, the per-module iso_<n> configurations, and the
resolveAll task — lives once in resolve-support.gradle, which every template applies via apply from. So
a resolution-logic fix is a one-file change, and a new Jmix line is just a small shape template. When a new
version needs a new repository, it goes in as static data in that version's template; the common Jmix repos
are never touched.
Because every per-run value (version, plugin version, repositories, module list) arrives as a -P property,
each generated template is a normal runnable Gradle script and can be run by hand for debugging:
gradle resolveAll -PjmixVersion=2.8.0 -PjmixPluginVersion=2.8.0 -PjmixModules=io.jmix.flowui:jmix-flowui -PisolatedResolution
gradle vaadinBuildFrontend -PjmixVersion=2.8.0 -PjmixPluginVersion=2.8.0 -PjmixModules=io.jmix.flowui:jmix-flowui
resolveAll resolves several configurations in a single Gradle build:
- all modules together — the graph a full app produces (
compileClasspath/runtimeClasspath). Always resolved. iso_<n>_api/iso_<n>_rt— each module in isolation, so the versions a subset of modules would select are captured too (under the Jmix BOM). Each module is resolved twice — as a compile classpath (api) and as a runtime classpath (rt) — because the two select different Gradle variants and neither is a superset:apicarries compile-only transitive deps (e.g. Guava's compile-onlyj2objc-annotations, whose version is pinned byguava-parent),rtcarries implementation/runtime-only deps.noBom_api/noBom_rt— explicitly-versioned libraries resolved without the BOM, same two variants.
These per-module configurations copy the resolution attributes of the real compileClasspath/runtimeClasspath.
A raw configuration with no attributes resolves a default/runtime variant and silently misses compile-only deps,
so a module isolated to an older library version would fail to collect that version's compile-only children.
The isolated configurations are created only when the -PisolatedResolution property is present. It is the
switch that turns on this extra per-module coverage: resolve-jmix and resolve-lib pass it automatically
(they want the widest possible set of versions), while resolve-npm does not — the Vaadin frontend build only
needs all modules together on the classpath, so the per-module configurations would be wasted work there.
Sources (-sources jars) are fetched for every resolved component unless --no-sources is passed; the
query is de-duplicated across the heavily-overlapping configurations. The isolated config names are zero-padded
to a fixed width so they process in numeric order.
Rejected-version recovery. Gradle downloads the jar of only the winning version of each module in a
conflict. A downstream project with a different module subset can legitimately select a non-winning version,
so those artifacts must be in the mirror too. After the normal resolve, resolveAll runs two complementary
recovery passes (both use a detached, conflict-free, transitive configuration to download; both loop to a
fixpoint; both skip packaging=pom BOMs/parent poms, which have no jar by design):
- Pass A — conflict losers, force-downloaded transitively. It walks every configuration's resolution
graph for versions that were requested but not selected (
requested != selected) and force-downloads each one transitively. Resolving the loser transitively re-expands the subtree that conflict resolution evicted, so the versions that subtree declares — which may never have been fetched at all, not even as a pom (e.g. an older Guava dropped before its subtree was explored, whosej2objc-annotationsversion is otherwise invisible) — are pulled in. Each recovered version's own graph is re-scanned for deeper losers. - Pass B — cache-scan mop-up. It scans the Gradle module cache for any version left with metadata
(
.pom/.module) but no main jar (a conflict loser whose pom was fetched during the normal resolve) and downloads the missing jar.
Together they cover both ways a needed version hides: one where its coordinate survives in the graph as a
rejected edge (Pass A, which also drags in its transitive closure), and one where only its pom reached the
cache (Pass B). --no-sources skips only the -sources jars, never the main jars.
resolve-npm runs Vaadin's vaadinBuildFrontend on the generated project and produces two lockfiles in
../npm-work/:
package-lock.json— the project's resolved npm graph. A stubpackage-lock.jsonis seeded first so Vaadin's post-build cleanup leaves it in place. This contributes the Jmix-specific packages (e.g.ace-builds, amCharts) that no shipped Vaadin bundle contains.dev-bundle-package-lock.json— thepackage-lock.jsonextracted from the resolvedcom.vaadin:vaadin-dev-bundle-<version>.jar(found by name, so it tracks whatever Vaadin the BOM selects). This is the framework's frozen version set (the whole Vaadin + dev/build toolchain), requiring no app run.
export-npm unions both lockfiles and, for each package, downloads (pure Java, straight from the registry):
- every resolved version, and
- every exact-pin variant — a version named exactly anywhere in either lock that differs from a resolved
one (looked up via the registry packument when it has no
resolvedURL).
The result mirrors both the frozen framework versions and the Jmix component packages, so an offline project resolves npm dependencies consistently in both dev and prod mode. Full semver-range mirroring is intentionally not done.
deptool does not embed the Gradle Tooling API. Each generated workspace gets a Gradle wrapper whose
distributionUrl is the version recommended for the Jmix line (from gradle-versions.properties, using the
checkpoint rule below), and the wrapper is run as a subprocess with --no-daemon. Consequences:
- The tool's own Gradle/JVM is decoupled from the daemon Gradle — one binary resolves 1.x (Gradle 7), 2.x (Gradle 8) and 3.x (Gradle 9.5) builds, each on its own wrapper-downloaded distribution.
--gradle-versionoverrides the recommended version per run.- The host JDK must run the whole resolution build for that line — its Gradle distribution, the io.jmix
Gradle plugin, and the framework's own Java baseline (the generated project mirrors a real Jmix app). Gradle
itself is lenient (7.x–9.x all run on JDK 17), so the Jmix line sets the real floor: 3.x requires JDK
21, 2.x runs on 17+, and 1.x needs JDK ≤ 17 (Gradle 7.6 can't run on JDK 21). So no single JDK spans 1.x
and 3.x; pick the JDK for the line you're resolving (the Docker image exposes a
BASE_IMAGEarg). --no-daemonmeans nothing lingers to lock the gradle-user-home thatexportlater walks.
WorkspaceManager selects a build template by checkpoint: a template build-<X.Y>.gradle means "this
project structure starts at Jmix X.Y", and the chosen one is the greatest checkpoint ≤ the requested
version:
checkpoints {1.0, 2.0, 2.1, 2.4, 2.4.2, 3.0}
2.0.5 -> build-2.0 2.1.0 -> build-2.1 2.8.0 -> build-2.4.2 3.4.0 -> build-3.0
So a template applies to its version and every later one until the next checkpoint — a template is added
only when the project structure actually changes (no per-minor churn), and a future version with no
checkpoint yet uses the latest one (logged). File names use precise versions (build-2.4.2.gradle), not
.x. The same checkpoint rule drives gradle-versions.properties.
When no version is supplied (resolve-lib <dep> for a plain library) the template is always
build-plain.gradle, a minimal Java-only template with no Jmix plugin/BOM. (resolve-lib keeps
--jmix-version for the case of a library that itself depends on Jmix modules: supplying it selects a Jmix
template so the BOM governs the transitive versions; omitting it uses build-plain.gradle.)
-
Add
dependencies-<minor>.xml(the module list) — this is usually the only step. There is no default fallback: resolving a version with no descriptor fails fast rather than shipping a generic/incorrect set.Optional
compileOnlydependencies must be listed explicitly. Some Jmix modules declare optional integrations ascompileOnly(e.g.net.sf.jasperreports:jasperreportsinjmix-reports, Hazelcast, Oracle EclipseLink).compileOnlyis not transitive and is absent from the published module POMs, so it cannot be discovered by resolving the modules — you must add these coordinates todependencies-<minor>.xml, version-less (the Jmix BOM manages their versions). Add a coordinate only if it is declared ascompileOnlyframework-wide: if any module also declares it as a normalapi/implementationdependency (e.g.spring-boot-starter-quartzviajmix-quartz), it is already resolved transitively — do not add it. To find the true set, survey the framework source:grep -rE "(api|implementation)[ (]+['\"]<group:artifact>" --include=*.gradle(excludingbom.gradle, whoseapientries are only BOM version constraints) — an empty result means it belongs in the list. -
(npm) nothing — the dev-bundle lock + variant collection handle missed versions automatically.
-
Only if the project shape changed at this version (a plugin added/removed, a settings block dropped, a new repository required): add a checkpoint template
build-<X.Y>.gradle. It applies from X.Y forward until the next checkpoint, so versions where nothing changed inherit the nearest earlier template. A new repository goes in as static data (the common Jmix repos are already shared). -
Only if the line uses a new Gradle baseline: add one line to
gradle-versions.properties(e.g.3.0 = 9.5.1).
A Java 17+ JDK is required to build the distribution.
gradlew zipDist # -> build/distributions/deptool-<version>.zip (bin/ + lib/)
gradlew installDist # -> build/install/deptool/bin/deptool[.bat]
Pass a version to stamp the archive:
gradlew zipDist -Pversion=1.2.3
After extracting the archive use bin/deptool or bin/deptool.bat to run CLI commands.


