Fork me on GitHub

Best practices Maven archetypes

This project maintains some Maven archetypes.

  • fr.ebruno.maven.archetypes:simple a simple standalone Java project.
  • fr.ebruno.maven.archetypes:simple-withparenta CI ready one with a complete parent POM.

An archetype to generate a simple standalone maven project.

To generate from the archetype :

mvn archetype:generate \
    -DarchetypeGroupId=fr.ebruno.maven.archetypes \
    -DarchetypeArtifactId=maven-archetype-simple

parameters can be passed directly :

mvn archetype:generate \
    -DarchetypeGroupId=fr.ebruno.maven.archetypes \
    -DarchetypeArtifactId=maven-archetype-simple \
    -DgroupId=demo.pkg \
    -DartifactId=MyApp \
    -Dversion=0.1.0-SNAPSHOT

In in the project directory, a maven wrapper should be generated to control Maven version for this project.

cd MyApp
mvn wrapper:wrapper 

The result can be compiled with maven

cd MyApp
./mvnw verify

and executed with maven :

./mvnw exec:java -Dexec.mainClass="demo.pkg.App"

but as the default App class is defined in a property in the ``pom.xml`, this could be simply :

./mvnw exec:java"

The jar can be launch with java but the slim jar fails because of missing dependencies.

java -cp target/MyApp-0.1.0-SNAPSHOT.jar demo.pkg.App
java -jar target/MyApp-0.1.0-SNAPSHOT.jar

A complete packaging (javadoc jar and shadedjar) can be done with :

./mvnw --activate-profiles javadoc,shadedjar verify
java -cp target/MyApp-0.1.0-SNAPSHOT-withdependencies.jar demo.pkg.App
java -jar target/MyApp-0.1.0-SNAPSHOT-withdependencies.jar 

Thanks to Jlink and a modular java project (jigsaw), it is possible to generate a minimal JRE with needed modules and dependencies.

./mvnw --activate-profiles javadoc,jlink clean verify
du -hs target/image

It is interesting to compare sizes of the shaded jar, the Jlink distribution (with an embeded JRE)

du -hs target/*-withdependencies.jar
du -hs target/image

a web site can be generated in target/site/index.html with :

mvn site
mvn  archetype:generate \
    -DarchetypeGroupId=fr.ebruno.maven.archetypes \
    -DarchetypeArtifactId=maven-archetype-withparent \
    -DgroupId=demo.pkg \
    -DartifactId=MyApp \
    -Dversion=0.1.0-SNAPSHOT

A parent pom is defined. By default the jar is “runnable” (see app.main.class in pom.xml)

cd Myapp
mvn verify
java -jar target/MyApp-0.1.0-SNAPSHOT.jar

WARNING: STILL WORK IN PROGESS

This project generates a complete Java+Maven project ready for Continuous Integration (CI). It is ready for GitFlow, SonarQube (tests, code coverage, …). It can produce signed artifacts, fat jars, slim runtime with jLink, native executables with GraalVM and container images. The build itself can also be done in a container.

The configuration is done with environment variables. For GitHub : GITHUBORG (GitHub account or organisation), GITHUBACTOR, GITHUBTOKEN, GITHUBORG (org or account) and optionally for SonarQube SONAR_URL and SONAR_TOKEN (Needs an instance of SonarQube)

For GitHub the CLI is needed.

Those variables have to be stored on the CI server (see GitHub Encrypted secrets). The script below transforms the local variables in GitHub secrets.

bash -c 'for secret in GITHUBACTOR GITHUBTOKEN SONAR_URL SONAR_TOKEN; do \
eval gh secret set $secret --app actions  \
                           --body ${!secret} \
                           --org ${GITHUBORG} \
                           --visibility all; \
done'

NOTE: Short version, a wrapper script is provided (READ IT BEFORE USE).

source <(curl -s https://raw.githubusercontent.com/ebpro/demomavenarchetype/develop/src/main/resources/archetype-resources/ci-wrappers.sh)
new-java-project <projectName> <groupId>

Generate each new project with this maven archetype (adapt at least the bold parameters).

mvn --batch-mode archetype:generate \
    -DarchetypeGroupId=fr.ebruno.maven.archetypes \
    -DarchetypeArtifactId=maven-archetype-withparent \
    -DgithubAccount=<b>$GITHUBORG</b> \
    -DgroupId=<b>demo.pkg</b> \
    -DartifactId=<b>MyApp</b> \
    -Dversion=0.1.0-SNAPSHOT

To enable deployment in the repo during the CI a deployment key is needed for each repository.

tmpKeydir=$(mktemp --directory /tmp/ci-wrappers.XXXXXX)
ssh-keygen -q -t ed25519 -C "An autogenerated deploy key" -N "" -f ${tmpKeydir}/key
gh repo deploy-key add --allow-write "${tmpKeydir}/key.pub"
gh secret set SECRET_DEPLOY_KEY < "${tmpKeydir}/key"
rm -rf tmpKeydir
  1. Initialize git environment for GitFlow (develop and master branches).
  2. Make a first commit.
  3. Create the GitHub repository with the GitHub Command line Interface.
  4. Create an orphan gh-pages branch for the website.
  5. Open the repository in a web browser.

git flow init -d && touch README.md && git add . && git commit -m "sets initial release." && \
  gh repo create ${GITHUBORG}/${PWD##*/} --disable-wiki --private  --source=. --push && \
    git checkout --orphan gh-pages && \
      git rm -rf . && touch index.html &&  \
      git add index.html && \
      git commit -m "sets initial empty site." && \
      git push --all \
    && git checkout develop && \
  gh repo view --web

The project uses the gitflow-maven-plugin to manage GitFlow for java+Maven projet (branches and artifact version).

It is possible to easily start and finish a feature (see version in pom.xml).

mvn -B gitflow:feature-start  -DpushRemote=true -DfeatureName=UI
mvn -B gitflow:feature-start  -DpushRemote=true -DfeatureName=DB

mvn -B gitflow:feature-finish  -DpushRemote=true -DfeatureName=DB
mvn -B gitflow:feature-finish  -DpushRemote=true -DfeatureName=UI 
mvn -B gitflow:release-start -DpushRemote=true -DallowSnapshots=true -DuseSnapshotInRelease=true
mvn -B gitflow:release-finish -DpushRemote=true -DallowSnapshots=true

You can compile and package with unit tests.

mvn package

You can compile and package with unit tests and integration tests.

mvn verify

You can execute with maven (see app.mainClass property in pom.xml).

mvn exec:java

The profile shadedjar generates a fat jar with all the classes from the transitive dependencies.

mvn -Pshadedjar clean verify
ls -lh target/*.jar
-rw-r--r--@ 1 bruno  wheel   3.1K May 20 16:09 target/MyApp-0.1.0-UI-SNAPSHOT-sources.jar
-rw-r--r--@ 1 bruno  wheel   2.6K May 20 16:09 target/MyApp-0.1.0-UI-SNAPSHOT-test-sources.jar
-rw-r--r--@ 1 bruno  wheel    38K May 20 16:09 target/MyApp-0.1.0-UI-SNAPSHOT-withdependencies.jar
-rw-r--r--@ 1 bruno  wheel   4.2K May 20 16:09 target/MyApp-0.1.0-UI-SNAPSHOT.jar

The application can then be executed without maven :

java -jar target/*-withdependencies.jar

Thanks to Jlink and a modular java project (jigsaw), it is possible to generate a minimal JRE with needed modules and dependencies.

mvn -Pjlink clean verify
du -hs target/image
 44M    target/image

The application can then be launched without a JRE installed.

./target/image/bin/myapp
Dec 02, 2022 11:18:56 PM fr.univtln.bruno.demos.App main
INFOS: Hello World! []

It is also possible to generate a native binary with GraalVM. An installation of GraalVM is needed and the package build-essential libz-dev and zlib1g-dev (zlib-devel, zlib-static et glibc-static). Only tested in linux.

sdk install java 22.3.r19-grl
sdk use java 22.3.r19-grl
mvn -Pnative clean verify
ls -lh target/testci
-rwxr-xr-x 1 bruno users 13M  3 déc.  00:12 target/testci
./target/testci
déc. 03, 2022 12:15:25 AM fr.univtln.bruno.demos.App main
INFO: Hello World! []

It is possible to build and run the project with just docker installed. A wrapper to run maven and java in a container but to work with the current directory is proposed. ~/.m2, ~/.ssh, ~/.gitconfig and the src directories are mounted. The environment variables needed for the project are also transmitted. The UID and GID are the one of the current user.

docker-mvn clean -P shadedjar package
docker run --rm \
  --mount type=bind,source="$(PWD)"/target/,target=/app,readonly \
  eclipse-temurin:17-jre-alpine \
    sh -c "java -jar /app/*-withdependencies.jar"

The file docker\Dockerfile is a multistage Dockerfile to build and deliver the application with several strategies (shaded jar, jlink, GraalVM) on several distributions (debian and alpine). To ease the use a wrapper for docker commands is provided in dockerw.sh

. ./ci-wrappers.sh
docker-wrapper-build
docker-wrapper-run

It is also possible to build all final target (Warning graalvm takes a long time to compile).

docker-wrapper-build-all

the result show the images and their size.

ebpro/testci   develop-finalShadedjarDebian   af3e072b35f7   2 hours ago   266MB
ebpro/testci   develop-finalShadedjarAlpine   38973a2aa588   2 hours ago   170MB
ebpro/testci   develop-finaljLinkDebian       db847ca5b281   2 hours ago   133MB
ebpro/testci   develop-finalJLinkAlpine       0cba42a81a33   2 hours ago   58.2MB
ebpro/testci   develop-finalGraalvmDebian     f5607a1e055f   2 hours ago   93.7MB
ebpro/testci   develop-finalGraalvmAlpine     d9c0573e4750   2 hours ago   18.8MB

It is also possible to run all the images :

docker-wrapper-run-all
INFO: Hello World! []
  0,06s user 0,04s system 6% cpu 1,529 total
Running ebpro/testci:develop-finalShadedjarAlpine
Dec 05, 2022 4:37:06 PM fr.univtln.bruno.demos.App main
INFO: Hello World! []
  0,05s user 0,05s system 5% cpu 1,724 total
Running ebpro/testci:develop-finaljLinkDebian
Dec 05, 2022 4:37:07 PM fr.univtln.bruno.demos.App main
INFO: Hello World! []
  0,05s user 0,05s system 5% cpu 1,690 total
Running ebpro/testci:develop-finalJLinkAlpine
Dec 05, 2022 4:37:09 PM fr.univtln.bruno.demos.App main
INFO: Hello World! []
  0,04s user 0,05s system 5% cpu 1,723 total
Running ebpro/testci:develop-finalGraalvmDebian
Dec 05, 2022 4:37:10 PM fr.univtln.bruno.demos.App main
INFO: Hello World! []
  0,05s user 0,03s system 7% cpu 1,161 total
Running ebpro/testci:develop-finalGraalvmAlpine
Dec 05, 2022 4:37:12 PM fr.univtln.bruno.demos.App main
INFO: Hello World! []

If a sonarqube server is available (i.e with https://github.com/ebpro/sonarqube). Set the variable SONAR_URL and SONAR_TOKEN

mvn -P jacoco,sonar \
  -Dsonar.branch.name=$(git rev-parse --abbrev-ref HEAD | tr / _) \
  verify sonar:sonar 
mvn clean verify
mvn -DskipTests=true \
    -Dsonar.branch.name=$(git rev-parse --abbrev-ref HEAD | tr / _) \
    -P jacoco,sonar \
    sonar:sonar
mvn site:site