ADR-0045: RESTest with jbang, from a catalog of the ISA group's own
Status: Accepted Date: 2026-11-01
Context
Section titled “Context”RESTest is installed with Docker, with the release archive and a Java 21 or later, with Homebrew on macOS and Linux, and its modules are on Maven Central (ADR-0032, ADR-0040, ADR-0044). Nothing of that reaches a Windows machine with no Java, and none of it is one command on all three systems.
jbang runs a Java program from its Maven coordinates, on macOS, Linux and Windows, and downloads a
Java first when the machine has none. What it runs can be named in a catalog: a file
jbang-catalog.json whose aliases each say which coordinates to run, which class to start, which
Java and which options to give it. jbang <alias>@<organisation> reads the catalog of the repository
<organisation>/jbang-catalog on GitHub, jbang <alias>@<address> the catalog at that address, and
jbang app install with either writes a command of the alias's name which runs it.
What jbang 0.142.0's own code says (its scripts in src/main/scripts/, its Java in
src/main/java/dev/jbang/, and devkitman 0.4.9, which finds and installs its Javas):
- An alias has
script-ref,main,javaandruntime-options, among others (Alias.java).restest-cli's jar names no main class, so the alias has to. restest@isa-groupfirst looks for a catalog namedisa-groupin anyjbang-catalog.jsonof the current folder or a folder above it, and for a folderisa-group/there (CatalogRef.getLocal). Only then does it readhttps://github.com/isa-group/jbang-catalog/blob/HEAD/jbang-catalog.json, and when that answers with no catalog - before the catalog exists, or on a network that cannot reach GitHub -gitlab.com/isa-group/jbang-catalogandbitbucket.org/isa-group/jbang-catalogin turn (ImplicitCatalogRef). On 1 November 2026 the nameisa-groupwas taken on GitLab, by a group or an account that is not public and that this record does not know; on Bitbucket a public workspace "ISA Group" held three of the ISA group's own projects, last changed in 2013 and 2014.- A catalog's address on github.com, gitlab.com or bitbucket.org,
raw.githubusercontent.comincluded, is turned back into its organisation's name (Catalog.simplifyRef) and looked for the same way. Any otherhttps://address is read as it is, with neither the local lookup nor the other hosts (Util.isRemoteRef). - A catalog read from the network is kept for 12 hours (
cache-evict,PT12H);--freshreads it again. The libraries resolved for an alias are kept by the text of its coordinates (DependencyCache), so an alias naming a version range, orRELEASE, would run the first version a person ever resolved, for good. - jbang's script starts jbang only on a JDK: it looks for
javac, and with none downloads a JDK 17 for itself. For a program asking21+, jbang uses any Java 21 or later it finds throughJAVA_HOME, thePATHor its own folder, and with none downloads the newest. - The first
jbang app installputs jbang'sbinon thePATH- in~/.bashrc,~/.bash_profile,~/.zshrcand fish's configuration, or in the user'sPathon Windows - whenever thatbinis not on thePATHalready (AppSetup.needsSetup), and on a machine with no Java of its own also setsJAVA_HOMEthere to jbang's JDK 17 (AppSetup.guessWithJava). - jbang's scripts hand on the number the program ends with:
jbangwitheval "exec …",jbang.cmdwithexit /b %ERRORLEVEL%, after turning delayed expansion off so that a!in an argument survives. PowerShell runsjbang.ps1and the.ps1of an installed command rather than the.cmd; it runs the program throughInvoke-Expression. jbang's own failures end with numbers of its own: 1 when it cannot fetch a library, 2 when it can neither find nor install a Java, or finds no catalog of the name it was given, 3 when it cannot download a catalog it knows, 4 on an internal error; its script ends with curl's number when its own download fails.
What was measured on 1 November 2026, on a Mac with Apple silicon, with an alias naming
es.us.isa.restest:restest-cli:2.4.0, io.restest.cli.Restest, --enable-native-access=ALL-UNNAMED
and 21+, against Maven Central:
- The class path jbang ran RESTest on was the 2.4.0 archive's
lib/, 71 jars, byte for byte. The POMs ADR-0044 publishes serve jbang as they are. versionanswered 0, a wrong option 2, and a run against an address where nothing listens 3 with its files:check-restest.sh --own-javapassed every step but the one with no Java, whosePATHheld five tools, among which neither jbang's script nor jbang itself could start.- With no Java at all - no
JAVA_HOME, nojavaorjavacon thePATH, and a jbang that had never run - jbang downloaded a JDK 17 for itself, 309 MB, and the newest, Java 27, 296 MB, for RESTest, whose continuous integration tests 21, 25 and 26; asked for21instead, a Java 21, 335 MB. The jars took 37 MB. Each took some 20 seconds. WithJBANG_DEFAULT_JAVA_VERSION=21, one JDK 21 served both. - In a folder holding
isa-group/jbang-catalog.jsonwhose aliasrestestnamed a program of its own,jbang restest@https://github.com/isa-group/jbang-catalog/blob/HEAD/jbang-catalog.jsonran that program; given the catalog at an address of another host, a local server, jbang ran RESTest. restest versiontook 0.32 seconds through the commandjbang app installwrote, against 0.15 through the archive's launcher, on the same Java 25, the median of ten each: jbang starts a Java of its own before RESTest's.
Decision
Section titled “Decision”1. A catalog of the ISA group's own, with one alias, read from GitHub Pages
Section titled “1. A catalog of the ISA group's own, with one alias, read from GitHub Pages”isa-group/jbang-catalog, holding jbang-catalog.json, with the alias restest, and published by
GitHub Pages as https://isa-group.github.io/jbang-catalog/jbang-catalog.json. The documentation gives
that address: a person types
jbang app install restest@https://isa-group.github.io/jbang-catalog/jbang-catalog.json for the
command restest, or jbang and the same alias to run it once. jbang reads it as it is, so neither a
jbang-catalog.json in the folder RESTest is run from nor an account on another host can stand in
for it. restest@isa-group reaches the same catalog through the lookups above, and the documentation
says so; the plan named that form, and the maintainer chose it until the lookups were found, on
1 November 2026, the day of this record.
2. The alias names what the archive's launchers name
Section titled “2. The alias names what the archive's launchers name”{ "script-ref": "es.us.isa.restest:restest-cli:<version>", "main": "io.restest.cli.Restest", "java": "21+", "runtime-options": ["--enable-native-access=ALL-UNNAMED"]}- The coordinates of
restest-cliat one release's version, whose POM names every library it runs with (ADR-0044 §3): the catalog names each release, since a range would not move anybody on. - The main class and the option are the launchers' (ADR-0032 §5). Without the option, Java 24 and later print four warnings before RESTest sends anything.
21+, what the launchers accept: jbang uses any Java 21 or later the machine has, and downloads nothing then. On a machine with none it downloads the newest, Java 27 on the day of this record, which RESTest's continuous integration does not test; RESTest is Java 21 bytecode, which a later Java runs. Chosen by the maintainer on 1 November 2026 against21, which Homebrew's formula does for the reason ADR-0040 §2 gives.
3. Kept in RESTest, filled in for each version, and held to the launchers
Section titled “3. Kept in RESTest, filled in for each version, and held to the launchers”dist/jbang/jbang-catalog.json is the catalog with @VERSION@ in place of the version, and
dist/jbang/catalog.sh fills it in, refusing anything but a version such as 2.5.0 or
2.5.0-SNAPSHOT. The archive does not carry it. The main class, the option and the Java are now said
in two places, so JbangAliasTest, among the architecture and documentation rules, fails whenever the
alias and the two launchers disagree on any of them, and when the class has no main.
4. Checked before a release is drafted, and on three systems before the catalog is written
Section titled “4. Checked before a release is drafted, and on three systems before the catalog is written”.github/scripts/check-jbang.shruns a catalog with jbang 0.142.0, pinned by its SHA-256, in a folder of its own: jbang's Javas, its libraries and the home folder its Java writes in are all there, and gone when it ends. It checks that with no Java at all, a jbang that has never run starts RESTest of that version, having downloaded two Javas; thatjbang app installmakes the command; that jbang runs RESTest on the archive'slib/, byte for byte; that the command passescheck-restest.sh --own-java, on Windows ascmd.exeruns it and through PowerShell's.ps1too, through Windows PowerShell,powershell.exe, which every Windows has; and that the firstapp installwrote no start-up file and, on Windows, did not change the user'sPathorJAVA_HOME.check-restest.sh --own-javaruns its steps with no Java with thePATHas it is, everyjavaandjavactaken out by.github/scripts/without-java.sh, in place of the five tools a launcher needs: a command with its own Java may need more than a launcher, and jbang does.release.yml'scheck-jbangruns it on Linux for the version just built, with RESTest's modules taken from what Maven Central gets and every other library from Central, and the draft waits for it. What depends on the build is the POMs and the jars, the same on every system..github/workflows/jbang.ymlruns it on Linux, macOS and Windows, against Maven Central and the release's archive, before it writes the catalog.
5. Written into the catalog once the release is published
Section titled “5. Written into the catalog once the release is published”jbang can only fetch what Maven Central serves, so the alias goes into the catalog from jbang.yml,
started by a release's publication, which follows Central's (ADR-0044 §5),
or by hand with a tag. It checks that Central serves the version, fills in the catalog, downloads the
release's archive with no token and checks it against checksums_sha256.txt, runs the check above on
three systems, writes RESTest's alias - and nothing else, so that other aliases the group adds stay -
never over a higher version, and over the same version only when the alias changed; then, once
GitHub Pages serves the new catalog, which may take some minutes, runs the check again on Linux from
the address a person types, which is what it adds and the same on every system. A run whose alias is
in the catalog already checks it there again, which is how a failed check of the published catalog is
made again. A pull request that touches the catalog or its scripts runs the first two steps against
the latest published release, and writes nothing.
The catalog is written with a deploy key, kept in an environment of RESTest's, jbang-catalog,
which only master and version tags may use, as Homebrew's tap is
(ADR-0040 §5).
6. What it promises
Section titled “6. What it promises”The latest release: a catalog names one version, which a person's jbang reads again within 12 hours
of a release, or at once with --fresh. A version of one's choosing is the image or the archive. On
Windows, cmd.exe and PowerShell are checked; the shell script jbang also writes there, for Git Bash,
is not.
Consequences
Section titled “Consequences”- jbang runs RESTest from the catalog on macOS, Linux and Windows, with no Java installed first.
The first time, on a machine with no Java, jbang downloads some 600 MB of Java into
~/.jbangand 37 MB of libraries into~/.m2/repository; on one with a JDK 21 or later, the libraries alone. Every start then costs jbang's own Java, some 0.17 seconds, nothing beside a run's budget. - jbang's own failures end with 1, 2, 3 or 4, before RESTest starts, numbers RESTest gives other
meanings: a build that cannot reach Maven Central ends as one that found faults. jbang says why in
lines beginning
[jbang]and writes noreport.json.docs/command-line.mdlists them among the numbers RESTest does not choose. restest@isa-groupcan run something else: what ajbang-catalog.jsonin the folder it runs in, or above it, names, and, whenever GitHub does not answer with the catalog, what a catalog ofisa-groupon GitLab or Bitbucket names. The documentation gives the address on GitHub Pages, and says why. Who holdsisa-groupon GitLab is not known.- A build of RESTest installed with
./mvnw installat a release's version puts that version's modules into~/.m2/repositorywith the build's own POMs, which name fewer libraries than the published ones; jbang then runs those, and fetches nothing from Central for them. - The first
jbang app installchanges the shell's start-up files, or the user'sPathon Windows, when jbang'sbinis not on thePATHyet; on a machine with no Java it pointsJAVA_HOMEat jbang's JDK 17, which the archive's launcher refuses with 127 in every shell started afterwards. The documentation says so, and how to undo it. - A release now also waits on GitHub for jbang itself and on the servers jbang downloads Java from,
foojay and Adoptium. jbang's version is moved by hand, in
check-jbang.sh. - A tag does not freeze the catalog, as it does not freeze Homebrew's formula: a fix to the alias reaches the catalog by the next run for that version.
- An owner of the organisation sets it up once: the catalog's repository, GitHub Pages for it, its
deploy key, and the environment that holds the key. Until
jbang.ymlfirst runs, by hand for 2.4.0 once this record is merged, the catalog does not exist.
Alternatives considered
Section titled “Alternatives considered”- A version range, or
RELEASE, in the alias, so that the catalog never changes and needs no workflow and no key: jbang keeps what it resolved by the text of the coordinates, so nobody would move to a later version. 21, as Homebrew's formula has: RESTest would always run on the Java its continuous integration and its image use, for the same download on a machine with no Java; but somebody whose only Java is 22 or later would download a Java 21, 335 MB. The maintainer chose21+.- A catalog in RESTest's own repository,
jbang-catalog.jsonat its root, asjbang restest@isa-group/RESTest: no second repository and no key, but a file at the root of RESTest, a longer command, and a catalog naming a release only once the pull request that movesmasteron has been merged. restest@isa-groupin the documentation, the command the plan named: short, but open to a localjbang-catalog.jsonand to GitLab and Bitbucket, as above. The maintainer chose it with a warning, while the catalog's address on github.com was believed to avoid the local lookup; it does not, since jbang turns it back intoisa-group.- Maven coordinates in place of a catalog,
jbang app install --name restestwith the main class, the option, the Java andes.us.isa.restest:restest-cli:<version>: no catalog to look up at all, but some 150 characters, and one version for good, installed again by hand at each release. - A main class in
restest-cli's jar: jbang would find it, but the option and the Java would still be the alias's, and the jar would say a third time what the launchers say. - Written into the catalog by hand at each release: one more step that can be forgotten, checked on a person's machine.
- The latest jbang in the checks: what a person installs, but a download that changes under every run; a jbang pinned by its checksum, moved by hand, instead.
- The three systems in
release.ymltoo: each of them downloading two Javas before every draft, for what does not depend on the build. - One jar holding every library, as ADR-0015 and ADR-0032 turned down.