1 - What is it?
Publish where you want (and more)!
Njord is a Maven 3 and Maven 4 extension that offers local staging, repository operations and repository publishing.
More precisely, Njord is a Resolver 1.x and 2.x extension (is yet another RepositoryConnector
)
that is loaded via Maven extensions mechanism and extends Resolver. Njord does not mingle with your build at all.
Njord supports “templates”, that define repository properties. Based on template a repository (“artifact store” in
Njord lingo) is created. And those repositories can be merged, redeployed, dropped, validated or published (and
more coming). Also, based on chosen template, Resolver is configured to deploy mandatory checksums for you. On the
other hand, mandatory signatures are NOT generated, it is you who must provide them as part of your build.
In short, Njord keeps things “as before” (as without it): user never had to worry about checksums (Resolver did generate
them always), while the signatures are usually provided by a plugin enabled in “release profile”. Njord goes step
forward, and IF user selects any “SCA” template
(“SCA” stands for “Stronger Checksum Algorithms”, see Resolver Checksums)
it will configure Resolver, and it will implicitly generate required stronger checksums without any user intervention required.
For now, templates supported out of the box are:
Name | Mode | Mandatory Checksums | Mandatory | Redeploy allowed? |
---|
release | RELEASE | SHA-1 , MD5 | GPG (Sigstore optional) | no |
release-sca | RELEASE | SHA-512 , SHA-256 , SHA-1 , MD5 | GPG (Sigstore optional) | no |
release-redeploy | RELEASE | SHA-1 , MD5 | GPG (Sigstore optional) | yes |
release-redeploy-sca | RELEASE | SHA-512 , SHA-256 , SHA-1 , MD5 | GPG (Sigstore optional) | yes |
snapshot | SNAPSHOT | SHA-1 , MD5 | GPG (Sigstore optional) | no |
snapshot-sca | SNAPSHOT | SHA-512 , SHA-256 , SHA-1 , MD5 | GPG (Sigstore optional) | no |
The njord:
URI
Njord can be considered also as new transport for Maven. It defines the njord:
URI prefix and supports several
forms:
njord:
URI is a shorthand for njord:release-sca
(default template), see below.njord:<TEMPLATE>
is equivalent to njord:template:<TEMPLATE>
, see below.njord:template:<TEMPLATE>
URI when deployed to, will create new store using given template.njord:store:<STORE>
URI when deployed to, will try to deploy to given store that already must exist (will not be created).
Similarly, to use Njord URIs in cases like maven-deploy-plugin
parameter for alternate deployment repository, the format
accepts id::uri
formatted string, so Njord URIs looks like id::njord:
or id::njord:template:release-sca
with one
important detail: the id
repository ID is there only to fulfil syntactical requirements, is unused otherwise.
Store name will be determined at the moment of creation.
Example URIs:
njord:
- means “use default template and create a new store” that is release-sca
.njord:snapshot
- means “use template by name snapshot
and create a new store”.njord:store:release-00001
- means “select existing store release-00001
and use that”.
Basically by setting up your POM with distribution release repository using URL njord:
and snapshot repository
using URL njord:snapshot
you are ready to use Njord. But, Njord is not intrusive, you can still use it by
doing nothing in your project and just deploying with -DaltDeploymentRepository=id::njord:
as well.
Hints:
2 - Using it
Using Njord!
Short explanation how to use Njord. It is totally non-invasive, but still you can integrate it as well with your
project. The example below is not touching (modifying) the project it is about to publish.
There is only one required thing: the extension must be loaded. Still, the extension is non invasive, and
remains fully dormant, does not interfere with your build at all, merely defines the njord:
transport.
Setting it up
Njord can be added as POM build extension:
<extensions>
<extension>
<groupId>eu.maveniverse.maven.njord</groupId>
<artifactId>extension</artifactId>
<version>${maveniverse.release.njordVersion}</version>
</extension>
</extensions>
Alternatively, with Maven 3 create project-wide, or with Maven 4+ create user-wide ~/.m2/extensions.xml
like this:
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>eu.maveniverse.maven.njord</groupId>
<artifactId>extension</artifactId>
<version>${currentVersion}</version>
</extension>
</extensions>
It is recommended (but not mandatory) to add this stanza to your settings.xml
as well if you don’t have it already
(to not type whole G of plugin):
<pluginGroups>
<pluginGroup>eu.maveniverse.maven.plugins</pluginGroup>
</pluginGroups>
Next, set up authentication. Different publishers require different server, for example sonatype-cp
publisher
needs following stanza in your settings.xml
:
<server>
<id>sonatype-cp</id>
<username>USER_TOKEN_PT1</username>
<password>USER_TOKEN_PT2</password>
</server>
Supported publishers and corresponding server.id
s are:
Make sure your settings.xml
contains token associated with proper server.id
corresponding to you publishing service you want to use.
That’s all! No project change needed at all.
Using it
Next, let’s see an example of Apache Maven project (I used maven-gpg-plugin
):
- For example’s sake, I took last release of plugin (hence am simulating release deploy):
git checkout maven-gpg-plugin-3.2.7
- Deploy it (locally stage):
mvn -P apache-release deploy -DaltDeploymentRepository=id::njord:
(The id
is really unused, is there just to fulfil deploy plugin syntax requirement. The URL njord:
will use “default” store template that is RELEASE. You can target other templates by using, and is equivalent of this njord:release
. You can stage locally snapshots as well with URL njord:snapshot
. Finally, you can target existing store with njord:store:storename-xxx
). - Check staged store names:
mvn njord:list
- Optionally, check locally staged content:
mvn njord:list-content -Dstore=release-xxx
(use store name from above) - Optionally, validate locally staged content:
mvn njord:validate -Ddetails -Dstore=release-xxx
(use store name from above) - Publish it to ASF:
mvn njord:publish -Dstore=release-xxx -Dtarget=apache-rao
(use store name from above) - From now on, the repository is staged on RAO, so you can close it, vote, and on vote pass, release it. All the usual fluff as before.
- Drop locally staged store:
mvn njord:drop -Dstor=release-xxx
(use store name from above)
Check out Maven generated plugin documentation for more mojos.
3 - Configuring it
Using Njord!
Njord uses existing Maven infrastructure to get the configuration, still a bit more explanation is needed for some bits.
For start, user interacts with Njord via njord:
URI using vanilla Maven plugins like maven-deploy-plugin
is, and
also using njord-maven-plugin
Mojos. All the mojos does not require projects to be run (and are also aggregator
Mojos). Still, IF Mojos are invoked with Maven Project present (ie in a checkout where POM is present, and Maven loads
it) the Project will be used as “extra contextual source” for some operations.
Njord basedir
By default, Njord uses ~/.njord
directory (in user home) directory as basedir. All the stores (locally staged artifacts)
are here. This directory may also contain a plain Java properties file njord.properties
to define some workstation-wide
Njord configuration (usually not needed). The file full path is ~/.njord/njord.properties
.
Njord properties
Njord applies following “precedence” rule to calculate effective (configuration) properties:
- Maven system properties
- Njord system-wide properties (sourced from
~/.njord/njord.properties
) - Maven project properties (if present)
- Maven user properties
This implies, that if you want to define for example njord.dryRun
property, you can achieve it in multiple ways: it is
possible even to have it in (effective) Project properties set by some profile. But be warned: in this example case, the
property will be defined ONLY if you invoke Njord Mojos in this very same project using very same active profiles!
Basic Maven stuff.
Of course, the recommended way to set this very property from example is Maven user property like mvn -Dnjord.dryRun ...
.
Project
Njord Mojos does not require project, and they can be invoked without any. But, in that case all the “heuristics” will be
unavailable, and you will need to provide all the required input to Mojos explicitly. For example, assuming you have
locally staged myproject-00001
, you can still publish it by explicitly configuring publish
mojo from a directory
where no project exists:
$ mvn njord:publish -Dstore=myproject=00001 -Dpublisher=sonatype-cp
Naturally, your user wide settings.xml
should be configured for this: authentication tokens should be set for server sonatype-cp
.
When project is present, it implies several things:
First, “prefix” is known (top level project artifactId
), in this
example case, it would be myproject
. When prefix is known, Njord will use simple heuristics, and will implicitly use
last (newest) store prefixed with this prefix (so if you have myproject-00001
and myproject-00002
the latter would
be selected).
Second, if project is present, and if distributionManagement
is configured (usually is for projects being deployed),
then Njord can deduce the publisher using the server.id
from POM and the server configuration present in your settings.xml
.
4 - Migrating projects
Using Njord!
Here will try to explain required steps to migrate your project to Njord. Assuming Maven basic knowledge, and some
experience about existing publishing to Central, using phased out services like Sonatype OSS or Sonatype S01 are.
Also, assuming you do have a project already, set up and probably published used via these legacy services.
Setup your namespace on Sonatype portal
Not much to say here, just follow the guide.
Do not forget to enable SNAPSHOTS for namespace, if you intend to use them.
Edit your settings.xml
and add your tokens to it. One of the main goals of Njord is to prevent copy-pasta
happening in your Maven Settings. Users publishing one namespace may not experience this, but users publishing multiple
namespaces are currently forced to copy-paste their auth tokens, as each project usually “invent” their own
distribution management server IDs (exception is ASF, where ASF parent POM contains “well known” server ID).
Hence, Njord recommend to name and store your tokens only once in your Maven Settings (this example is for
Sonatype Central Portal):
<server>
<id>sonatype-central-portal</id>
<username>$TOKEN1</username>
<password>$TOKEN2</password>
</server>
Next, we will add a bit of Njord configuration: what publisher we want to use with this server, and what templates.
Edit the server entry above to contain something like this:
<server>
<id>sonatype-central-portal</id>
<username>$TOKEN1</username>
<password>$TOKEN2</password>
<configuration>
<!-- Sonatype Central Portal publisher -->
<njord.publisher>sonatype-cp</njord.publisher>
<!-- Releases are staged locally (if omitted, would go directly to URL as per POM) -->
<njord.releaseUrl>njord:template:release-sca</njord.releaseUrl>
<!-- Snapshots are staged locally (if omitted, would go directly to URL as per POM) -->
<njord.snapshotUrl>njord:template:snapshot-sca</njord.snapshotUrl>
</configuration>
</server>
And that’s it! Your settings.xml
now contains auth for Sonatype Central Portal, and also tells Njord which publisher
to use with this server (it is sonatype-cp
), and which templates to use.
Note: if you want to use Central Portal Snapshots feature, then don’t forget to first enable these on Portal Web UI.
Next, in that case you can remove the njord.snapshotUrl
element, and enjoy “direct deploy” (so Njord does not
meddle, or stage snapshots). Maven will go directly for Central Portal Snapshot endpoint. Any service that supports
SNAPSHOT deploy and accepts maven-deploy-plugin
deploy requests may be left without njord.snapshotUrl
configuration
as in that case that good old deploy plugin can do the job as well, no Njord needed.
Setup your project
As your project was already published to Central, the POM may contain distribution management like this:
<distributionManagement>
<snapshotRepository>
<id>myproject-snapshots</id>
<name>My Project Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>myproject-releases</id>
<name>My Project Releases</name>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
You do not have to change anything!. But, it is worth to change it. For start, the URLs for releases here are “fake”,
and some tools like SBOM engines cannot use them as intended, as the URL is not “where artifacts are published”, it is
“service used to publish” instead. It is recommended to change this POM section into something like this:
<distributionManagement>
<snapshotRepository>
<id>sonatype-central-portal</id>
<name>My Project Snapshots</name>
<url>https://central.sonatype.com/repository/maven-snapshots</url>
</snapshotRepository>
<repository>
<id>sonatype-central-portal</id>
<name>My Project Releases</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</distributionManagement>
Yes, you see it right: POM now says the truth: “we publish to Central” and “we use Sonatype Central Portal” service.
Also, there is no need to distinguish server for “release” and “snapshot”.
Finally, IF you have some existing plugin that did the job before, just remove, and undo all the hoops and loops that
the plugin or tool required (like adding some properties, profiles, whatnot).
And you are done!
Extension
Finally, you need to make sure that Njord extension is loaded as extension. Ideally as POM project/build/extensions:
<extensions>
<extension>
<groupId>eu.maveniverse.maven.njord</groupId>
<artifactId>extension</artifactId>
<version>${maveniverse.release.njordVersion}</version>
</extension>
</extensions>
But you can do it as core extension, in .mvn/extensions.xml
or Maven 4 user wide ~/.m2/extensions.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>eu.maveniverse.maven.njord</groupId>
<artifactId>extension</artifactId>
<version>${maveniverse.release.njordVersion}</version>
</extension>
</extensions>
If you are putting Njord into POM, it is recommended to tie plugin version to extension version, by adding
plugin management entry to POM/build/pluginManagement/plugins as:
<plugin>
<groupId>eu.maveniverse.maven.plugins</groupId>
<artifactId>njord</artifactId>
<version>${maveniverse.release.njordVersion}</version>
</plugin>
Summary
To get summary, invoke mvn njord:status
.
It will tell you all publishing related information about your project, even is the auth present or not (ie you may
have a typo in server.id in POM or your settings.xml
).
With this setup as above, one should see output like this (example is from BOM builder plugin),
with added right hand “annotated explanations”:
[cstamas@angeleyes bom-builder-maven-plugin (master)]$ mvn njord:status
[INFO] Scanning for projects...
[INFO] Njord 0.6.0 session created
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] eu.maveniverse.maven.bom-builder:bom-builder [pom]
[INFO] eu.maveniverse.maven.plugins:bom-builder3 [maven-plugin]
[INFO] eu.maveniverse.maven.bom-builder:it3 [pom]
[INFO]
[INFO] ------------< eu.maveniverse.maven.bom-builder:bom-builder >------------
[INFO] Building eu.maveniverse.maven.bom-builder:bom-builder 1.1.1-SNAPSHOT [1/3]
[INFO] from pom.xml
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- njord:0.6.0:status (default-cli) @ bom-builder ---
[INFO] Project deployment:
[INFO] Store prefix: bom-builder <------------------------------------------------ store prefix, build-builder-00001, etc
[INFO] * Release
[INFO] Repository Id: sonatype-central-portal
[INFO] Repository Auth: Present <------------------------------------------------- auth is present
[INFO] POM URL: https://repo.maven.apache.org/maven2/ <--------------------------- POM "truth", once published, artifacts are here
[INFO] Effective URL: njord:template:release-sca <-------------------------------- The Njord URL we use when deploy releases
[INFO] - release-sca <-------------------------------------------------------------- The template we set, detailed
[INFO] Default prefix: 'bom-builder'
[INFO] Allow redeploy: false
[INFO] Checksum Factories: [SHA-512, SHA-256, SHA-1, MD5]
[INFO] Omit checksums for: [.asc, .sigstore, .sigstore.json]
[INFO] * Snapshot
[INFO] Repository Id: sonatype-central-portal
[INFO] Repository Auth: Present
[INFO] POM URL: https://central.sonatype.com/repository/maven-snapshots/ <-------- Snapshots do not use Njord, mvn deploy deploys directly Portal
[INFO]
[INFO] No candidate artifact stores found <----------------------------------------- Nothing has been locally staged yet
[INFO]
[INFO] Project publishing:
[INFO] - 'sonatype-cp' -> Publishes to Sonatype Central Portal <-------------------- The publishing service we set, detailed
[INFO] Checksums:
[INFO] Mandatory: SHA-1, MD5
[INFO] Supported: SHA-512, SHA-256
[INFO] Signatures:
[INFO] Mandatory: GPG
[INFO] Supported: Sigstore
[INFO] Published artifacts will be available from:
[INFO] RELEASES: central @ https://repo.maven.apache.org/maven2/
[INFO] SNAPSHOTS: sonatype-central-portal @ https://central.sonatype.com/repository/maven-snapshots
[INFO] Service endpoints:
[INFO] RELEASES: sonatype-central-portal @ https://central.sonatype.com/api/v1/publisher/upload
[INFO] SNAPSHOTS: sonatype-central-portal @ https://central.sonatype.com/repository/maven-snapshots
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for eu.maveniverse.maven.bom-builder:bom-builder 1.1.1-SNAPSHOT:
[INFO]
[INFO] eu.maveniverse.maven.bom-builder:bom-builder ....... SUCCESS [ 0.037 s]
[INFO] eu.maveniverse.maven.plugins:bom-builder3 .......... SKIPPED
[INFO] eu.maveniverse.maven.bom-builder:it3 ............... SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.329 s
[INFO] Finished at: 2025-05-24T22:49:03+02:00
[INFO] ------------------------------------------------------------------------
[INFO] Njord session closed
[cstamas@angeleyes bom-builder-maven-plugin (master)]$
Publish it!
With this setup, you just perform mvn deploy
as you did before. If your checkout is snapshot, you will deploy to
Central Portal Snapshots. If your checkout is a release, it will be locally staged once Maven finishes.
To publish, just use mvn njord:publish
, or just do mvn deploy -Dnjord.autoPublish
.
Once Maven returns, your project is being validated at https://central.sonatype.com/publishing
Check out Maven generated plugin documentation for more mojos.
What if don’t want (or cannot) change the POM?
In this case Njord can still be used, but the “inconvenience” is that you need to hand over all info to Njord.
The maven user settings change is mandatory, you need to have tokens set in your settings.xml
.
The presence of Njord extension is mandatory as well, You can load it as user-wide extension (~/m2/extensions.xml
)
if you use Maven 4 or you can create (or edit if exists) project .mvn/extensions.xml
.
Below I assume you work with a release checkout (ie you checked out a tag, also the “release profile” in this example
follow ASF convention):
$ mvn -P apache-release deploy \ 1)
-DaltDeploymentRepository=id::njord: \ 2)
-Dnjord.autoPublish \ 3)
-Dnjord.publisher=sonatype-cp \ 4)
-Dnjord.publisher.sonatype-cp.releaseRepositoryId=sonatype-central-portal 5)
So what happens here? We invoke “deploy for release” (1), but using “alternate deployment repository” (2),
that will create an artifact store from default template. Note that id::url
form is standard format accepted
by maven-deploy-plugin
but the id
is in fact unused, as store ID will be known only after it is created.
At session end (3) the created store with deployed artifacts will be published, using specified publisher service (4)
and auth material from specified server (5) in user settings.xml
.