Crawling it yourself
The Jenesis Module Index is produced by an ordinary Java program, and that program is available to run yourself. Reach for it when the public index is not the one you need. That might be a mirror your network can reach when Central is not, an internal repository whose modules are never published publicly, or an experiment with a different ownership policy.
Everything here runs from source with a JDK 25 and no build step, because the JDK compiles it on demand:
java sources/build/jenesis/crawler/Crawl.java \
https://maven-central.storage-download.googleapis.com/maven2/ \
https://repo.maven.apache.org/maven2/.index/
The crawl
Crawl is the program that produces an index. It takes two URIs, and neither has a default: the first is
where artifacts are fetched from, the second where the repository's index is read. Pairing them separately
is deliberate, so a fast, range-supporting mirror can serve the jars while the canonical location serves
the index. Give one URI and it is used for both.
From there the crawl streams the index and, for every artifact that looks like a jar, fetches just enough
of the file to read whether it declares a module name. What it finds lands under data/, in the same
tab-separated files the service reads, so a finished crawl is directly servable.
A run is bounded rather than exhaustive, because a full sweep of Maven Central takes many hours. It works to a wall-clock budget, checkpoints as it goes, and resumes exactly where it stopped, so an index is built up over as many runs as it takes. Try it against a small budget first:
java -Djenesis.crawler.data=smoke-data -Djenesis.crawler.budget=3 \
sources/build/jenesis/crawler/Crawl.java <artifact-uri> <index-uri>
The knobs you are likely to touch:
| Property | Default | Effect |
|---|---|---|
jenesis.crawler.data |
data |
Where the index and the crawl's own state are written. |
jenesis.crawler.budget |
180 |
Wall-clock minutes this run may spend scanning before it stops cleanly. |
jenesis.crawler.concurrency |
64 |
Artifact fetches in flight, which is what bounds peak memory. |
jenesis.crawler.resume |
true |
Set false to restart the index sweep; already-scanned artifacts are still skipped. |
There are more - checkpoint frequency, tail size, git publishing - and the project's README lists them.
When the repository has no index
The file the crawl streams is a Maven Indexer index (nexus-maven-repository-index.gz), and that is not
part of the Maven repository layout - Maven itself never reads it. Maven Central publishes one and a
repository manager can be configured to generate one, but plenty of real repositories serve none at all.
Point Crawl at one of those and there is nothing to stream.
The way in is then the part of the layout every Maven repository does have: maven-metadata.xml, which
lists an artifact's versions. LoadCoordinates reads it for coordinates you name, and ReconcileMetadata
for coordinates already recorded. An index of an internal repository is therefore built by naming what it
publishes rather than by sweeping an index:
java sources/build/jenesis/crawler/LoadCoordinates.java \
https://nexus.example.com/repository/maven2/ com.example:widgets com.example:gadgets
java sources/build/jenesis/crawler/Regenerate.java
LoadCoordinates writes audit rows only. Run Regenerate afterwards to derive the resolved views the
service reads; until then there is nothing to serve.
The companion programs
An index is never finished in one pass, so the crawler ships as a family of small programs around the
main one. Each is a main class you run the same way; the ones that read the index honour
jenesis.crawler.data unless noted.
Programs that fetch from the repository:
| Program | What it does |
|---|---|
RetryFailed |
Re-scans artifacts recorded as permanently failed, optionally narrowed by a regex over the recorded error. What you run after fixing a scanner bug. |
ReconcileMetadata |
Recovers versions the repository index omits, by diffing each recorded artifact's maven-metadata.xml against what was scanned. |
LoadCoordinates |
Scans every version of a groupId:artifactId you name, read from its maven-metadata.xml - a targeted add rather than a sweep, and the way into a repository with no index. |
IndexProbe |
Streams a remote index and prints the records matching a search term, plus a histogram of the extensions it carries. A forensic tool for "why was this artifact never scanned?". |
Programs that only read what is on disk:
| Program | What it does |
|---|---|
Regenerate |
Rebuilds the resolved views from the audit log, which is how an ownership or filtering change is rolled out without re-fetching a single jar. -Djenesis.crawler.regenerate.scope=artifacts|modules narrows it, -Djenesis.crawler.regenerate.dry.run=true previews it. |
SetOwners |
Applies an ownership policy from a properties file - module name to a comma-separated list of groupId or groupId:artifactId owners - writing each module's owners.tsv and regenerating its views. Publishers not listed are rejected. |
ListOwners |
Prints the current owners of every module matching a glob, in the format SetOwners reads, so you can review and edit a policy before applying it. |
DriftReport |
Writes the drift report. With -Djenesis.crawler.drift.emit=<category> it also writes a SetOwners file proposing an owner for every module in that category. |
ModuleSummary |
Regenerates the coverage summary. |
TopModules |
Writes a top-modules report for each data/top/<year>.txt list you pass; -Djenesis.crawler.top.bleeding=true produces the bleeding-edge variant. |
ModuleMaven |
Prints the named modules of the index as a flat <module-name>=<groupId>:<artifactId> properties stream, for a tool that only needs the mapping. Reads data/modules/ in the working directory. |
The reports chapter describes what the summary, top-modules and drift reports contain;
the public index regenerates them daily. The checked-in owners-republisher-fixes.properties in the
project is a worked example of an ownership policy, ready for SetOwners.
Serving your own index
An index on disk becomes a service by publishing its data/modules/ tree over HTTP - a static file host,
an object store, or a git hosting service's raw view all work - and pointing the reference service at it.
The service reads DATA_BASE, the HTTP(S) base URL of that tree, and redirects to ARTIFACT_BASE, your
artifact mirror; both are plain environment variables. With those two set, the URL
shapes are unchanged, so every client that resolves against the public service
resolves against yours without knowing the difference.