Resolving through repo.jenesis.build

The Jenesis Module Index is served as an HTTP service at repo.jenesis.build. You ask for a module name, optionally a version, and a file name. The service answers with a 302 redirect to the real file, on Google's Maven Central mirror by default. Nothing is re-hosted: the module index only decides which Maven artifact a module name maps to and points you at it.

The whole contract is a small, stable set of URL shapes, so anything that can follow a redirect is a client - curl -L, a browser, or the Jenesis build tool. Every redirect is derived from one row of a plain text file, and you can read those files directly if you would rather resolve yourself.

The four routes

The path segment before the module name selects the route. The route decides which version space you are addressing and which kind of module it can serve:

Route URL shape Version segment is…
artifact /artifact/<module>[/<mavenVersion>]/<file> The Maven coordinate version. Any file extension passes through verbatim. Serves named and automatic modules.
module /module/<module>[/<moduleVersion>]/<file>.jar The module-info version the publisher declared. Named modules only.
sources /sources/<module>[/<moduleVersion>]/<file>.jar The module-info version; the redirect appends -sources to the Maven file name. Named modules only.
documentation /documentation/<module>[/<moduleVersion>]/<file>.jar The module-info version; the redirect appends -javadoc to the Maven file name. Named modules only.

Two version spaces are in play: /artifact/ is keyed by the Maven version you see in a POM, the other three by the module-info version the publisher embedded. Pick the route that matches the version you hold.

The <file> segment is required and its name must start with the module name; everything after that is the extension, either after a . or after -<classifier>.. /artifact/ passes any extension through, while the other three accept .jar, optionally followed by .asc for the detached OpenPGP signature or by .sigstore.json for the Sigstore bundle - which follows the -sources / -javadoc decoration, so the signature served is the one over that jar. For a checksum sidecar, use /artifact/.

Only a named module - one shipping a real module-info.class - is reachable through /module/, /sources/ and /documentation/. An automatic module, which only sets Automatic-Module-Name, resolves through /artifact/ alone. The modules the JDK itself ships, such as java.base, are left out of the named routes too, because the JDK's own copy always shadows a jar of that name. On /artifact/ such a name serves whichever artifact declared it, under the usual rules. Other java.* names, such as java.xml.bind, resolve on every route.

Versions are optional

Leave the version segment out and the service returns the newest release, by Maven's version ordering with pre-releases passed over: org.slf4j resolves to 2.0.19 even though 2.1.0-alpha1 ranks above it.

A version is a pre-release when a qualifier of it ranks below the release in Maven's own ordering - alpha, beta, milestone (including the a1, b2, m3 shorthands), rc and its cr alias, snapshot - or when it is one of the qualifiers Maven's ordering does not know but publishers use for the same purpose: ea, pre, prerelease, preview, dev, nightly, canary, next, test, adhoc. Everything else is a release, so 1.0.0.Final, 33.0-jre and 1.0-sp1 resolve as ordinary versions. It is the rule the build tool applies to a STABLE version.

# 2.0.19, the newest release of org.slf4j
curl -L -O https://repo.jenesis.build/artifact/org.slf4j/org.slf4j.jar

# 2.1.0-alpha1, the newest version of any kind
curl -L -O -H 'Jenesis-Prerelease: true' https://repo.jenesis.build/artifact/org.slf4j/org.slf4j.jar

# A specific version, pinned
curl -L -O https://repo.jenesis.build/artifact/org.slf4j/2.0.9/org.slf4j.jar

Jenesis-Prerelease: true drops the filter. Without it, a module that has only ever published pre-releases answers 404 with a body naming the header that would have served it, while a version asked for by name is served either way. The response carries the header back when what it served is a pre-release, and every redirect is sent with Vary: Jenesis-Prerelease.

A named version matches exactly - no ranges, no normalisation. One the index has not recorded is still answered: the service assumes it exists under the module's newest coordinate, redirects there, and flags that with Jenesis-BestEffort: true, which is what keeps a release from the last few hours resolvable before the crawl records it. Jenesis-BestEffort: false asks for a 404 instead of a guess; any other value, or none, keeps it. If the redirect target - the mirror, or the repository the request named - has no such file, it answers 404.

artifact route: every file of a coordinate

On the artifact route the extension is opaque: whatever follows the module name becomes the suffix of the Maven file name. Because the extension passes straight through, one route serves the jar, the POM, its checksums and signatures, and Gradle module metadata:

# The jar
GET /artifact/org.slf4j/org.slf4j.jar
→ 302 …/org/slf4j/slf4j-api/2.0.19/slf4j-api-2.0.19.jar

# The POM of a specific version
GET /artifact/org.slf4j/2.0.9/org.slf4j.pom
→ 302 …/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.pom

# A checksum, or a signature - same pattern
GET /artifact/org.slf4j/2.0.9/org.slf4j.pom.sha256
→ 302 …/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.pom.sha256

# Gradle module metadata, if the publisher provides it
GET /artifact/org.slf4j/2.0.9/org.slf4j.module
→ 302 …/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.module

The file is always requested as <module>.<extension>, never as <artifactId>-<version>.<extension>, and there is no maven-metadata.xml. The Jenesis build tool speaks exactly this shape, so for it the /artifact/ route is a complete repository. A stock Maven or Gradle resolver asks for Maven-shaped file names and is not a client of this route.

module, sources, and documentation routes

These three are keyed by the module-info version, serve named modules only, and accept .jar, .jar.asc or .jar.sigstore.json. They map to the main jar, the sources jar, and the javadoc jar of the same artifact:

GET /module/org.slf4j/2.0.9/org.slf4j.jar
→ 302 …/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar

GET /sources/org.slf4j/2.0.9/org.slf4j.jar
→ 302 …/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9-sources.jar

GET /documentation/org.slf4j/2.0.9/org.slf4j.jar
→ 302 …/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9-javadoc.jar

GET /module/org.slf4j/2.0.9/org.slf4j.jar.asc
→ 302 …/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar.asc

GET /module/net.bytebuddy/1.18.14/net.bytebuddy.jar.sigstore.json
→ 302 …/net/bytebuddy/byte-buddy/1.18.14/byte-buddy-1.18.14.jar.sigstore.json

Few publishers upload a Sigstore bundle yet, and the service redirects without checking that one exists, so a request for a bundle the release lacks follows the redirect to a 404.

A named release whose declared module-info version differs from its Maven version is left out of these routes, because the service promises that the two agree (see the guarantee below). Such a release remains reachable through /artifact/ under its Maven version.

Classifiers

A classifier is the part of the file name between the first hyphen and the next dot. It switches the lookup to the classifier-scoped view of the module index and becomes a standard Maven classifier on the redirect target:

GET /artifact/p6spy/p6spy-all.jar
→ 302 …/p6spy/p6spy/3.9.1/p6spy-3.9.1-all.jar

The same works on every route: <module>-<classifier>.jar under /module/ resolves the classifier's jar of a named module.

Where the redirect points

The Location is Google's Maven Central mirror, which carries the same artifacts and is not rate limited the way Central is. Send Jenesis-Repository: <url> to be redirected to another Maven repository instead: the URL replaces the mirror as the base the artifact path is appended to, so Jenesis-Repository: https://repo.maven.apache.org/maven2/ redirects to Maven Central itself, and the URL of a repository manager redirects to it. A missing trailing slash is added. The value must be an absolute http or https URL without credentials, a query or a fragment; anything else is answered with 400. No header, or a blank one, keeps the mirror.

The mirror trails Central by a couple of hours, so a release published since its last sync is not there yet. That is one case for the header. A redirect carrying Jenesis-BestEffort: true is the likeliest, because it was built for a version the index has not recorded, which usually means a very new one.

# Google's mirror
curl -sI https://repo.jenesis.build/artifact/org.slf4j/org.slf4j.jar | grep -i '^location'

# Maven Central itself
curl -sI -H 'Jenesis-Repository: https://repo.maven.apache.org/maven2/' \
     https://repo.jenesis.build/artifact/org.slf4j/org.slf4j.jar | grep -i '^location'

The 302 response

A successful response is an empty-bodied HTTP 302 whose Location points at the Maven URL. It is cached with Cache-Control: public, max-age=<ttl>, stale-while-revalidate=86400, where the TTL is an hour on the public service. A redirect to a repository named by Jenesis-Repository is marked private instead, so a shared cache never hands one client's repository to another.

The resolved coordinate is echoed back as response headers, so a client can record exactly what it fetched without parsing the Location:

Header When Value
Jenesis-GroupId always Maven groupId of the resolved row.
Jenesis-ArtifactId always Maven artifactId.
Jenesis-MavenVersion always Maven coordinate version.
Jenesis-ModuleVersion /module/, /sources/, /documentation/ The publisher-declared module-info version. Omitted on /artifact/, where the lookup key is already the Maven version.
Jenesis-BestEffort a version the index has not recorded true - the redirect was built from the module's newest coordinate rather than from a recorded row. Sent to the service as false, it declines such a redirect instead.
Jenesis-Prerelease the version served carries a pre-release qualifier true - the version was asked for by name, or the request opted in to pre-releases.
Vary always Jenesis-Prerelease, Jenesis-Repository, Jenesis-BestEffort - the redirect depends on all three request headers, so a shared cache must key on them.

When a request fails

Status Meaning
400 The Jenesis-Repository header is not a usable repository URL.
404 Nothing could be served. The body says why when the module or version is the problem.
405 The request was not GET or HEAD.
500 The service failed unexpectedly.
502 The upstream index files are temporarily unreachable.

A 404 has one of these causes:

  • the path is not one of the four shapes, or the file name does not start with the module name;
  • the file name has no extension, or a .jar-only route was asked for another extension;
  • the module name is unknown to the index, or has no named release on a /module/-family route;
  • no version was asked for and every version the module has published is a pre-release, without Jenesis-Prerelease: true on the request to accept one;
  • a version was asked for that the index has not recorded, and the request sent Jenesis-BestEffort: false rather than accept a redirect built from the newest coordinate;
  • the module has no resolved owner in this view.

Stability guarantee

The service makes one promise you can build on. A recorded (module, moduleVersion) always resolves to the same Maven artifact, and that artifact's Maven version is the same number as the module version. Pin a module version in your build and every later rebuild resolves to the identical jar. Maven itself does not enforce unique module versions; the index does.

The /artifact/ lookup is stable for the same reason: Maven coordinates are immutable on Central. The resolution of a name shifts only when the ownership of that name is re-decided - usually by an operator's explicit policy, occasionally because an older publication of the name is discovered later.

A module resolves only if some artifact on Maven Central declared that module name. If you get a 404 for a name you expected, the artifact may ship neither a module-info nor an Automatic-Module-Name, or it may be an automatic module you asked for on /module/ - try /artifact/. The reports show what is and is not covered.

Using it from the build tool

The Jenesis build tool points at repo.jenesis.build out of the box, and the layout decides which route it uses. The default modular_to_maven layout translates each requires into a Maven coordinate through /artifact/, so named and automatic modules alike resolve. The strict modular layout resolves purely by module name through /module/, so every dependency must be a named module. That difference is the whole reason a plain-jar library is reachable in one layout and not the other.

Three settings move a build to another deployment of the module index; the Dependencies chapter of the build tool covers them in full, and shows which settings the build passes on as request headers:

Setting Environment variable Purpose
-Djenesis.module.uri=<url> JENESIS_REPOSITORY_URI The base URL of the module index (default https://repo.jenesis.build/).
-Djenesis.module.token=<token> JENESIS_REPOSITORY_TOKEN Sent verbatim as the Authorization header (for example Bearer …) to the first index jenesis.module.uri names, never to the default service.
-Djenesis.module.local=<dir> JENESIS_REPOSITORY_LOCAL The local module repository consulted first (default ~/.jenesis).

A build can also skip the service and read the index itself, which Reading the index directly describes.

From the command line, curl -L is all a manual lookup needs:

# Follow the redirect and save the jar
curl -L -O https://repo.jenesis.build/module/com.fasterxml.jackson.databind/com.fasterxml.jackson.databind.jar

# Inspect only - see the redirect target and the coordinate headers
curl -I https://repo.jenesis.build/module/com.fasterxml.jackson.databind/com.fasterxml.jackson.databind.jar

Pointing at a mirror

The URL shapes are the contract, so any deployment that serves the same shapes is a drop-in replacement. The reference service is a small HTTP function that reads four optional environment variables:

Variable Default Purpose
DATA_BASE the index data on raw.githubusercontent.com An HTTP(S) base URL the resolved-view files are fetched from. Point it at a fork or mirror to serve a different index.
ARTIFACT_BASE Google's Maven Central mirror The base URL the 302 redirects target when a request does not name one with Jenesis-Repository. Point it at a Maven mirror or proxy.
HOME_REDIRECT the project's GitHub page Where a request for / redirects.
REDIRECT_TTL 3600 (seconds) The max-age on the 302, and the edge-cache TTL for the upstream reads.

Setting ARTIFACT_BASE changes only the default: a client's Jenesis-Repository header still overrides it.

Any number of path segments before the route marker are ignored, so the same service works whether it is mounted at /, /mod/, or /jenesis/v1/, with no configuration.

Reading the index directly

You do not have to go through the service at all. Each redirect comes from one row of a resolved view, a plain tab-separated file you can read over raw.githubusercontent.com or any mirror - enough to build a resolver of your own. Each module has a directory whose path mirrors its dot-separated name (com.fasterxml.jackson.core → com/fasterxml/jackson/core/). It holds up to four files:

File Role
versions.tsv The audit log: every (groupId, artifactId, version) that has ever declared this name, append-only in publication order, never pruned.
artifacts.tsv The resolved view keyed by Maven version, read by /artifact/.
modules.tsv The resolved view keyed by module-info version, read by /module/. Present only when the owner publishes named releases.
owners.tsv An optional ownership policy: which publishing groupIds are allowed or rejected for this name.

artifacts.tsv has four columns, sorted version-descending:

2.0.10  named      org.slf4j  slf4j-api
2.0.9   named      org.slf4j  slf4j-api
1.7.36  automatic  org.slf4j  slf4j-api

The columns are version, type (named or automatic), groupId, artifactId. Find the row whose first column is your version and fetch <artifactId>-<version> from Maven Central.

modules.tsv has four columns, sorted module-version-descending, and lists named releases only:

2.0.10  org.slf4j  slf4j-api  2.0.10
2.0.9   org.slf4j  slf4j-api  2.0.9

The columns are moduleVersion, groupId, artifactId, mavenVersion. Match the first column, then fetch the coordinate named by the last three. Classifier-scoped variants live alongside as artifacts-<classifier>.tsv and modules-<classifier>.tsv.

A tool that needs only the mapping can read data/module-maven.properties instead, regenerated daily. It lists named modules as <module>=<groupId>:<artifactId>, one per line, for each name that starts with its owner's groupId or a known alias of it, such as kotlin for org.jetbrains.kotlin.

The build tool reads them this way on request, so a project that would rather not depend on the service can have its build resolve module names from the data and fetch the coordinates from the Maven repository it already uses:

java -Djenesis.module.source=git build/jenesis/Make.java

jenesis.module.index (or JENESIS_INDEX_URI) points that at a fork or a mirror of the data instead of the published files, and jenesis.module.prerelease and jenesis.module.speculative decide the same two questions they decide for the service - whether a module asked for without a version may resolve to a pre-release, and whether a version the data does not record may be fetched from the module's newest coordinate anyway.

A module name is not a namespaced or authoritative identifier - it is just a string a jar carries, and unrelated artifacts can and do declare the same one. The resolved views already pick a single owner per name for you, using the audit log and the ownership policy; How the index is produced explains how that owner is chosen, and the drift report lists the names still in dispute. If you resolve directly, pin the (groupId, artifactId) you expect rather than trusting a name on its own.