Resolver & checksums
Curie talks to Maven Central directly — it resolves transitive closures, verifies checksums, and supports mirrors, credentials, and per-artifact repository routing. For declaring dependencies and BOM imports, see Adding dependencies.
Transitive resolution
Curie does a breadth-first walk of every dependency's POM, applying Maven's conflict-resolution rules:
- Nearest wins — when the same
group:artifactappears at two depths, the shallower occurrence's version is used. - First declared wins at equal depth.
- Top-level BOM beats transitive explicit — your
[bom-imports]overrides any version a transitive POM hard-codes.
Nearest-wins is silent for minor and patch differences, but a major-version mismatch (a discarded candidate whose major differs from the kept version) fails the build rather than silently shipping a possibly-incompatible JAR. Resolve it by upgrading, excluding the offending transitive dependency, or accepting it with allowVersionConflict = true on the dependency — see Major-version conflicts.
A pom-packaged (aggregator) dependency — one whose POM declares <packaging>pom</packaging>, such as org.codehaus.groovy:groovy-all — has no JAR of its own. Curie expands it for its <dependencies> and contributes no jar for the aggregator itself, whether it appears transitively or is declared directly in Curie.toml.
POMs are fetched serially in phase 1 (small, fast). JARs are then downloaded in parallel in phase 2 — up to eight concurrent workers — and arrive in the local cache in a stable, topologically ordered classpath.
Checksum verification
Every artifact Curie downloads is verified against its .sha256 sidecar from the same repository. The sidecar hash is then persisted next to the artifact in ~/.m2/repository/...artifact.jar.sha256, mirroring Maven Central's published layout. On the next build the cached hash is compared against the cached JAR — zero network calls, zero implicit trust.
A missing sidecar is a hard error. Every well-formed Maven repository publishes one — Maven's deploy plugin generates them, and Nexus / Artifactory generate them server-side on upload. A missing sidecar usually means a misconfigured proxy or a manually-uploaded artifact, and either way Curie refuses to install an unverifiable JAR.
Fallback to SHA-1
If a repository only publishes .sha1 sidecars (older mirrors, some private repos), Curie falls back to that. SHA-1 is cryptographically weak for authentication but still catches accidental corruption and unsophisticated substitution — adequate for the integrity-check use case.
Tamper detection
Edit a JAR in ~/.m2/repository by hand and the next build will fail with a checksum mismatch error citing the artifact and both digests. The local cache is now tamper-evident.
Offline mode
Pass --offline to forbid network calls entirely. Any cache miss is an immediate error. Useful for sealed CI environments, reproducibility checks, and incident-response builds where you want to be sure nothing on disk has changed since the last good build.
Building greeter v0.1.0 Compile up to date Tests up to date Package up to date Done target/greeter-0.1.0.jar
Pre-fetching dependencies
curie fetch downloads artifacts into ~/.m2/repository without compiling or testing anything — useful for warming the cache before --offline builds, in CI, or in air-gapped environments.
With no arguments, it downloads everything declared in Curie.toml: [dependencies], [test-dependencies], [annotation-processors], and [test-annotation-processors], with BOM-managed versions resolved.
Dependencies 3 JAR(s) Done 3 JAR(s) cached $ curie build --offline # succeeds entirely from the local cache
Pass one or more group:artifact:version coordinates to fetch extra artifacts and their full transitive closures — without adding them to Curie.toml:
Done 4 artifact(s) cached
If a transitive dependency declares a Maven version range (e.g. [2.9.1,2.11)), curie fetch queries maven-metadata.xml, picks the exact version Maven would select (the highest published version satisfying the range), and proposes a ready-to-run command that pins it — a supplied coordinate overrides the matching transitive range:
error: non-deterministic version ranges in dependency graph
com.google.code.gson:gson
[2.9.1,2.11) → 2.10.1
Re-run with an explicit version for each ranged artifact:
curie fetch ch.epfl.scala:bsp4j:2.1.1 com.google.code.gson:gson:2.10.1
(curie build, by contrast, always requires you to pin the range explicitly in Curie.toml — see Version range in dependency POM.)
Add --no-transitive to download just that one artifact's POM and JAR, skipping its dependencies:
Done ~/.m2/repository/org/apache/commons/commons-compress/1.27.1/commons-compress-1.27.1.jar
Bulk pre-fetch from a file
Pass --file <path> to read a list of coordinates from a file — one group:artifact:version per line. Blank lines and lines starting with # are ignored. The listed artifacts are resolved with parallel JAR downloads, making this much faster than fetching them one by one. If a transitive range is encountered, curie fetch names the coordinate line to add to the file (with the exact version computed from maven-metadata.xml); adding it pins the range so the file ends up listing the concrete versions you intend to use.
# JUnit org.junit.platform:junit-platform-console-standalone:6.1.0 org.junit.jupiter:junit-jupiter:6.0.3 # Jackson com.fasterxml.jackson.core:jackson-databind:2.17.2 # Plugins org.apache.maven.plugins:maven-surefire-plugin:3.5.6
Done 47 artifact(s) cached $ mvn clean verify # now works without internet access
This is the recommended way to warm the Maven local cache for air-gapped builds. The project's [[repositories]] entries are automatically included when the command is run inside a project directory, so artifacts from custom repositories resolve correctly too.
Per-dependency repository selection
By default every artifact is fetched from Maven Central. To pull a specific artifact from a different repository, declare the repository with a unique id and reference it from the dependency:
[[repositories]] id = "shibboleth" url = "https://build.shibboleth.net/nexus/content/repositories/releases/" [dependencies] # fetched from shibboleth only; its transitive deps try Central + shibboleth "net.shibboleth.oidc:oidc-common-crypto-api" = { version = "3.3.0", repository = "shibboleth" } # fetched from Maven Central only (no repository field) "com.fasterxml.jackson.core:jackson-databind" = "2.17.2"
The routing rules are precise: the explicitly-declared artifact is fetched from the named repository only; its transitive deps are tried in both Central and the named repository. Artifacts with no repository field never hit extra repositories.
Repository mirrors
Redirect artifact downloads through a corporate proxy or local cache without changing project config. Mirrors are per-machine and live in ~/.curie/config.toml:
# Route Maven Central through an internal Nexus proxy. [[mirrors]] id = "internal-nexus" mirror_of = "central" url = "https://nexus.internal/repository/maven-central/" # Use "*" to mirror every configured repository. [[mirrors]] id = "catch-all" mirror_of = "*" url = "https://nexus.internal/repository/all/"
Each repo gets its own independent mirror substitution. First matching mirror wins. Mirrors are transparent to the project — Curie.toml stays unchanged.
Repository credentials
Private repositories that require authentication are configured in ~/.curie/config.toml with indirection so secrets stay out of files:
[[credentials]] repo_id = "internal-nexus" username = "" password = ""
The repo_id matches the id field in [[repositories]]. is expanded at credential-use time — the string is read from the environment variable, not stored as a literal. Literal values are accepted too (for local-only configs where the credential is not sensitive).
Dependency tree and conflict traces
After a successful build, inspect the resolved closure with curie deps:
Dependencies for my-app v0.1.0
└─ com.fasterxml.jackson.core:jackson-databind:2.17.2
├─ com.fasterxml.jackson.core:jackson-annotations:2.17.2
└─ com.fasterxml.jackson.core:jackson-core:2.17.2
Use --why group:artifact to explain why a specific artifact is at its chosen version — especially useful when nearest-wins resolved a conflict:
com.fasterxml.jackson.core:jackson-core:2.17.2 (depth 1)
Introduced by:
[declared] → jackson-databind:2.17.2 → jackson-core:2.17.2 (chosen — depth 1)
No version conflicts.
Add --tests to inspect the test-dependency closure instead. See Dependency tree for the full reference.