This is an example project to demonstrate Maven usage and configuration.
- Create
README.md
. - Generate
.gitignore
- Make initial commit with
README.md
and.gitignore
.
mvn \
archetype:generate \
-DgroupId=com.github.jj3l.maven-example \
-DartifactId=maven-example \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
Replace pom.xml
with less noisy pom.yml
. This requires the Polyglot Maven core extension as
available with Maven v3.3.1.
mvn \
io.takari.polyglot:polyglot-translate-plugin:translate \
-Dinput=pom.xml \
-Doutput=pom.yml
rm pom.xml
mkdir -p .mvn
cat << 'EOF' > .mvn/extensions.xml
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>io.takari.polyglot</groupId>
<artifactId>polyglot-yaml</artifactId>
<version>0.2.1</version>
</extension>
</extensions>
EOF
The last line from pom.yml
(pomFile: {}
) must be removed.
Maven is not able to run with minimal POM without errors and warnings. The following adjustments to the super POM are necessary:
- Set Java version to 1.6 for the Java Compiler.
- Specify the Version of the project info report plugin.
build: plugins: - groupId: org.apache.maven.plugins artifactId: maven-project-info-reports-plugin version: 2.8.1 - groupId: org.apache.maven.plugins artifactId: maven-compiler-plugin version: 3.1 configuration: source: 1.6 target: 1.6
- Add informations required to deploy to the Maven Central Repository
description: This is an example project to demonstrate Maven usage and configuration. developers: - id: jjaekel name: Jonathan Jäkel email: j@j3l.de organization: Jonathan Jäkel organizationUrl: 'https://www.j3l.de/' roles: [Developer] timezone: +1 licenses: - name: Apache License, Version 2.0 url: https://www.apache.org/licenses/LICENSE-2.0.txt distribution: repo
Add to pom.yml
:
build:
plugins:
- groupId: org.apache.maven.plugins
artifactId: maven-source-plugin
executions:
- id: attach-sources
goals:
- jar
Add to pom.yml
:
build:
plugins:
- groupId: org.apache.maven.plugins
artifactId: maven-javadoc-plugin
executions:
- id: attach-javadocs
goals:
- jar
The OWASP Dependency check checks all dependencies for known security risks.
Add to pom.yml
:
build:
plugins:
- groupId: org.owasp
artifactId: dependency-check-maven
version: 3.0.2
executions:
- goals:
- check
See https://jeremylong.github.io/DependencyCheck/dependency-check-maven/ and Automatisierte Überprüfung von Sicherheitslücken in Abhängigkeiten von Java-Projekten (german).
Certain aspects of the project model can be enforced. There are even more aspect which could be enforced but are not supported by the plugin.
- Normalization of the POM (to reduce merge conflicts, make POM better readable). Partly solved by the tidy-maven-plugin.
- Deprecated/vulnerable dependencies/plugins could be bannend.
- Version is compliant to semantic versioning.
- There is a changelog compliant to keep a changelog.
Add to pom.yml
:
build:
plugins:
- groupId: org.apache.maven.plugins
artifactId: maven-enforcer-plugin
version: 3.0.0-M1
executions:
- id: no-duplicate-declared-dependencies
goals:
- enforce
configuration:
rules:
banDuplicatePomDependencyVersions: ''
- id: enforce
goals:
- enforce
configuration:
rules:
dependencyConvergence: ''
Add license informations to each file and add a license file.
Add to pom.yml
:
build:
plugins:
- groupId: org.codehaus.mojo
artifactId: license-maven-plugin
version: 1.14
configuration:
licenseName: apache_v2
addJavaLicenseAfterPackage: false
canUpdateCopyright: true
canUpdateDescription: true
canUpdateLicense: true
emptyLineAfterHeader: true
extraFiles:
DockerFile: properties
processStartTag: 'LICENSE_START'
processEndTag: 'LICENSE_END'
sectionDelimiter: '**'
executions:
- id: first
goals:
- update-file-header
phase: process-sources
- id: second
goals:
- update-project-license
It would be appreciated to remove processStartTag
, processEndTag
and sectionDelimitersectionDelimiter
(the
first section is not required). Trailing spaces should be removed. A LICENSE.md
would be preferred over
LICENSE.txt
. The placeholder Copyright [yyyy] [name of copyright owner]
will not be replaced.
To switch from jUnit 4 to jUnit 5 replace
dependencies:
-- groupId: junit
artifactId: junit
version: 4.12
by
dependencies:
- groupId: org.junit.jupiter
artifactId: junit-jupiter-api
version: 5.1.1
scope: test
in your POM. And add required dependencies to the maven-surefire-plugin
:
build:
pluginManagement:
plugins:
- artifactId: maven-surefire-plugin
version: 2.19.1
dependencies:
- groupId: org.junit.platform
artifactId: junit-platform-surefire-provider
version: 1.1.1
- groupId: org.junit.jupiter
artifactId: junit-jupiter-engine
version: 5.1.1
Links:
Note: To run the Tests in Eclipse with a module descriptor present it's required to add jUnit to the classpath in the Eclipse project. Eclipse provides a jUnit library for this (jUnit v5.1.0 currently).
- Right click on Project, select "Build Path" -> "Add Libraries..." -> "JUnit" -> Click "Next". Select "JUnit library version": "JUnit 5". Eclipse will no longer show compile errors for the test class.
- Right click on Project, select "Run As" -> "JUnit Test". Will be successful.
You can use release
instead of source
and target
to configure the Java version for the maven-compiler-plugin
now. Unfortunately the latest release v3.7.0 of the maven-compiler-plugin
is from 2017-09-01. To work with Java 10
a newer version of the asm
dependency must be configured explicitly to workaround this bug.
- artifactId: maven-compiler-plugin
version: 3.7.0
configuration:
release: 10
dependencies:
- groupId: org.ow2.asm
artifactId: asm
version: 6.2
One of the most powerful features of Maven is the repository concept to store packaged Java artefacts like JAR files. The default repository where dependencies are looked up is known as Maven Central Repository. By default the Maven Central Repository is available at https://repo.maven.apache.org/maven2. There are multiple mirrors like https://repo1.maven.org/maven2 or (local) company repositories.
To publish artefacts to Maven Central some requirements need to be met.
- Choose a
groupId
as your global unique namespace. Similar to the Java package naming convention it reuses the DNS in a reversed manner. This means you need a domain name you control. This could be any registered second level Domain. But a third level domain from your GitHub account likejj3l.github.com
will be sufficient. Having agroupId
you have to use thegroupId
in your POM and to signup for Sonatype Jira and register thegroupId
to be used publishing artefacts in the Maven Central Repository with. - Make your Sonatype Jira credentials available to Maven as they are used for OSSRH as well. Add to
~/.m2/settings.xml
:<settings> <servers> <server> <id>ossrh</id> <username>jjaekel</username> <password>1password</password> </server> </servers> </settings>
- The Sonatype Jira credentials will be referenced by the id
ossrh
. One time from themaven-deploy-plugin
via thedistributionManagement
releasing snapshot releases and one time from thenexus-staging-maven-plugin
configuration releasing production releases (see Snapshot Artefact vs. Release Artefacts for details). Add topom.yml
:distributionManagement: snapshotRepository: id: ossrh url: https://oss.sonatype.org/content/repositories/snapshots build: plugins: - groupId: org.sonatype.plugins artifactId: nexus-staging-maven-plugin version: 1.6.7 extensions: true configuration: serverId: ossrh nexusUrl: https://oss.sonatype.org/ autoReleaseAfterClose: true
- Add to
pom.yml
:build: plugins: - groupId: org.apache.maven.plugins artifactId: maven-release-plugin version: 2.5.3 configuration: autoVersionSubmodules: true useReleaseProfile: false releaseProfiles: release goals: deploy
- Generate a GPG key pair and publish the pubic key so everybody can verify the artefacts you signed with your private key.
- Install GPG Suite with MacGPG. The Maven GPG plugin does not have a Java GPG
implementation and needs the
gpg
executeable on thePATH
. - Generate GPG key pair:
gpg --batch --gen-key << EOF Key-Type: RSA Key-Length: 4096 Subkey-Type: RSA Subkey-Length: 4096 Name-Real: Jonathan Jäkel Name-Email: j@j3l.de Expire-Date: 0 Passphrase: <1password> EOF
- Get key ID:
gpg --list-keys pub rsa4096 2017-12-03 [SCEA] 183303E02CCE0907450FE9370046FC88CB105E68 <-- Key ID
- Publish public key
gpg --keyserver hkp://pgp.mit.edu --send-keys 183303E02CCE0907450FE9370046FC88CB105E68
- Configure the Maven GPG Plugin in your
pom.yml
:build: plugins: - groupId: org.apache.maven.plugins artifactId: maven-gpg-plugin executions: - id: sign-artifacts phase: verify goals: - sign configuration: keyname: ${gpg.keyname} passphraseServerId: ${gpg.keyname}
- To sign the GPG key passphrase has to specified as command line argument
mvn verify -Dgpg.passphrase=thephrase
or it will be prompted for. Even better would be to define the passphrase in thesettings.xml
. Add to~/.m2/settings.xml
:You can sign explicitly by running<settings> <servers> <server> <id>183303E02CCE0907450FE9370046FC88CB105E68</id> <passphrase>1password</passphrase> </server> </servers> <profiles> <profile> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <gpg.keyname>183303E02CCE0907450FE9370046FC88CB105E68</gpg.keyname> </properties> </profile> </profiles> </settings>
JAVA_HOME=$(/usr/libexec/java_home -v 10) mvn clean verify
but you will sign regularly implicit usingJAVA_HOME=$(/usr/libexec/java_home -v 10) mvn release:perform
later. You will find the generated signatures intarget/maven-example-*.*.asc
. - Using semantic versioning is not required but recommended.
Maven distinguish between snapshot and production releases. Production releases use common versioning schemes (
semantic versioning in best case). Snapshot releases use the same versioning scheme but add
a -SNAPSHOT
suffix. A snapshot release prepares the production release with the same prefix. Whereas a
production release denotes a immutable version with certain assertion like QA process or semantic versioning the
snapshot release can be assigned to multiple versions as floating tag.
Artifact build on a snapshot version can be deployed to the Sonatype snapshot repository but will not be synchronized to Maven Central. The
version in the pom.yml
will not be changed and no tag at GitHub will be set. For the artifacts stored in the
repository the -SNAPSHOT
suffix will be replaced with a timestamp. Maven references the latest timestamp for a
given SNAPSHOT
automatically.
For snapshot releases the default Maven process is used. The snapshot repository is configured under
distributionManagement
which will be looked up by the maven-deploy-plugin
.
To deploy a snapshot version run:
JAVA_HOME=$(/usr/libexec/java_home -v 10) mvn clean deploy
To issue a production release some more steps are required as it must be switched from current snapshot version to
a production release version (remove the -SNAPSHOT
suffix) and the next snapshots version must be bumped.
Additionally there may be rules like reviews and QA for the release process as for Maven Central. The QA is based
on the final release artifacts and can not be done before having the release artifacts available. But if the QA
fails the release artifacts should not be published.
For this reason it's not possible to publish into the Maven Central repository directly. Rather you have to publish your production release artifacts into the Sonatype Open Source Software Repository Hosting (OSSRH). The OSSRH make use of staging releases as a Nexus Repository Pro feature. A staging release is a temporary staging repository with the artifacts of a release candidate. The release candidate artifacts can be reviewed and discarded or promoted to the final release repository. Only the final release repository will be synchronized to the Maven Central Repository.
This makes it difficult to deploy production release artefacts to Maven Central using the maven-deploy-plugin
.
Nexus introduces the nexus-staging-maven-plugin
for this case(reference). Unfortunately the nexus-staging-maven-plugin
does not recognize the distributionManagement
. So the URL of
the OSSRH repository must be configured with the plugin configuration (as shown above).
To deploy a production version run:
git diff-index --quiet HEAD -- || echo 'There are local modifications!'
sed -i '' -E 's/^(version: ([0-9]{1,}\.){1,}[0-9]{1,})(.*)/\1/' pom.yml
grep '\-SNAPSHOT' pom.yml && echo 'There are SNAPSHOT dependencies!'
JAVA_HOME=$(/usr/libexec/java_home -v 10) mvn test
JAVA_HOME=$(/usr/libexec/java_home -v 10) mvn site-deploy
git add pom.yml
git commit -m 'Create production release'
git push
git tag "v$(grep '^version:' pom.yml|sed -E 's/^version: (.*)/\1/')"
git push origin "v$(grep '^version:' pom.yml|sed -E 's/^version: (.*)/\1/')"
JAVA_HOME=$(/usr/libexec/java_home -v 10) mvn deploy
# Change version number to next relase manually.
sed -i '' -E 's/^version: .*/&-SNAPSHOT/' pom.yml
git add pom.yml
git commit -m 'Bump to next snapshot release'
git push
Login at https://oss.sonatype.org/ with your Sonatype Jira credentials. Go to stagingRepositories, select your distributes staging repository and click Release
button.
The maven-release-plugin
does not work together with polyglot Maven(reference).
There is the criticism the maven-release-plugin
tries to make things simpler than they are(reference). A release process has a intrinsic complexity which can
not be eliminated by a tool.
Releases have to be carefully planned. They have to be announced and there may be strategies required to handle incompatible changes. And there may be tradeoffs to be considered (e.g tollerate downtimes vs. migration costs). Last but not least multiple conventions apply (like semantic versioning) and a QA process is required.
So what you want to do would be probably something like this:
- Checkout a clean copy of the current
master
branch. - Ensure there are no local modifications.
- Remove the
-SNAPSHOT
suffix from the version in the POM. Commit and push the changes. - Ensure there are no other snapshot dependencies as they are floating tags and you can't guarantee the released software will later behave as you tested.
- Run tests.
- Build and publish the project website.
- Tag a GitHub release.
- Upload the production release artifacts to the production release repository.
- Bump to a new snapshot version. Commit and push the changes.
- https://maven.apache.org/repository/
- https://blog.sonatype.com/2010/10/new-official-maven-central-repository-in-europe/
- http://central.sonatype.org/pages/ossrh-guide.html
- http://central.sonatype.org/pages/choosing-your-coordinates.html
- http://central.sonatype.org/pages/apache-maven.html
- http://blog.sonatype.com/2010/01/how-to-generate-pgp-signatures-with-maven/.
- https://maven.apache.org/plugins/maven-gpg-plugin/usage.html
- https://maven.apache.org/plugins/maven-release-plugin/
- https://maven.apache.org/plugins/maven-jarsigner-plugin/
You need an OAuth2 access token to allow Maven to publish the project website to GitHub on a behalf of you. Each commit will be done in your name. Consider using a dedicated "bot" user for this.
- Login at https://github.com/ with your user.
- Go to "Settings" -> "Developer settings" -> "Personal access tokens"
- Click on "Generate new token".
- Choose a token description like "Maven bot to publish project website."
- Select at least
public_repo
anduser:emailuser:email
scope. This limits the access to the minimum needed to publish the website at GitHub (Documentation).
Write the token into your settings.xml
:
cat << 'EOF' > ~/.m2/settings.xml
<settings
xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"
>
<servers>
<server>
<id>github</id>
<password>GENERATED_TOKEN</password>
</server>
</servers>
</settings>
EOF
There are two things important here:
- The credentials will be referenced by the ID
github
. - Through the absence of an
username
thepassword
is interpreted as OAuth2 token.
See https://developer.github.com/apps/building-integrations/setting-up-and-registering-oauth-apps/registering-oauth-apps/ for details to register an oauth2 app.
The maven-site-plugin
is not able to to push the generated site into a git repository. But GitHub provides a
plugin for this. Skip site deployment by the maven-site-plugin
and configure the GitHub plugin to push the site
to GitHub in pom.yml
:
- groupId: org.apache.maven.plugins
artifactId: maven-site-plugin
version: 3.4
configuration:
skipDeploy: true
- groupId: com.github.github
artifactId: site-maven-plugin
version: 0.12
executions:
- goals:
- site
phase: site-deploy
configuration:
server: github
message: Create project website for ${project.version}.
The maven-site-plugin
is available at Maven Central and refers
https://repo.eclipse.org/content/repositories/egit-releases/ as external repository which is discouraged and needs a workaround on CircleCI. As a workaround the
external repository must be referenced by the project using the maven-site-plugin
. So we have to add
additionally in pom.yml
:
# Required by com.github.github:github-maven-plugins-parent
# https://github.com/github/maven-plugins/blob/master/pom.xml
repositories:
- id: egit
name: Eclipse egit
url: https://repo.eclipse.org/content/repositories/egit-releases/
Unfortunately GitHub plugin does not use the /project/distributionManagement/site
information like the
maven-site-plugin
. Instead the project URL, the SCM metadata or an explicit configuration is used (first wins)
For the project URL this pattern should be used: https://github.com/REPOSITORY_OWNER/REPOSITORY_NAME
But better would be to use the SCM metadata and point the project URL to
https://REPOSITORY_OWNER.github.io/REPOSITORY_NAME
. This is the location there the generated project website
will be found.
Weblinks:
- https://help.github.com/articles/user-organization-and-project-pages/
- https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/
Run mvn site:effective-site
to view the default site descriptor:
<project
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.7.0 http://maven.apache.org/xsd/decoration-1.7.0.xsd"
name="maven-example"
xmlns="http://maven.apache.org/DECORATION/1.7.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<bannerLeft>
<name>maven-example</name>
</bannerLeft>
<publishDate />
<version />
<body>
<links>
<item name="maven-example" href="./" />
</links>
<menu ref="parent" />
<menu ref="reports" />
</body>
</project>
Add a contemporary skin built on Bootstrap:
<skin>
<groupId>org.apache.maven.skins</groupId>
<artifactId>maven-fluido-skin</artifactId>
<version>1.5</version>
</skin>
There is another skin built on Bootstrap. But even this one was last edited three years ago and has some issues with TLS
resources. So best for now will be to choose
maven-fluido-skin
.
See Site Descriptor documentation for further details.
Since v3.3 of the maven-site-plugin
Markdown syntax is supported without declaring a Doxia dependency explicitly. Add a Markdown document to src/site/markdown
.
With an additional suffix .vm
filtering is supported (resolve e.g. ${project.name}).
The maven-project-info-reports-plugin
genartes the default reports under "Project Documentation" ->
"Project Information" in the left navigation.
- Dependencies
- Dependency Information
- Distribution Management
- About
- Project License
- Plugin Management
- Project Plugins
- Project Team
- Source Repository
- Project Summary
Additional reports configured will be shown under "Project Documentation" -> "Project Reports" in the left navigation.
Reports available updates of Properties, Plugins and Dependencies.
Run tests at CircleCI. See .circleci/config.yml
for
CircleCI configuration.
java \
--module-path target/maven-example-1.1.0-SNAPSHOT.jar \
-m com.github.jj3l.maven.example/com.github.jj3l.maven.example.App
``
## Create a Custom-Runtime-Image and run the app as jImage
Create Custom-Runtime-Image:
```bash
$(/usr/libexec/java_home -v 10)/bin/jlink \
--module-path target/maven-example-1.1.0-SNAPSHOT.jar \
--add-modules com.github.jj3l.maven.example \
--launcher app=com.github.jj3l.maven.example/com.github.jj3l.maven.example.App \
--strip-debug \
--compress=2 \
--output target/maven-example-1.1.0-SNAPSHOT
Run the app:
target/maven-example-1.1.0-SNAPSHOT/bin/app
There is a Maven plugin to create Modular Runtime Images. The plugin has alpha status currently and need a multi module build.