Build, Test and Package Java with Maven and Jenkins
A reliable Maven Pipeline starts from a clean checkout, selects an explicit JDK and Maven runtime, resolves dependencies through an owned repository path, publishes test evidence and produces one immutable artifact. Jenkins should orchestrate the repository build rather than reimplement Maven lifecycle logic in Groovy.
Start from a known controller checkpoint
Use the accepted controller and agent checkpoint from the prior lesson. Record the Jenkins version, Java runtime, active configuration, plugin inventory and current Git revision before changing this boundary.
java -version
sudo systemctl is-active jenkins
sudo journalctl -u jenkins -n 50 --no-pager
curl -fsS http://127.0.0.1:8080/login >/dev/nullUnderstand the operating boundary
| Decision | Implementation | Evidence |
|---|---|---|
| Scope | Name the controller, folder, job, node and environment | The selected boundary is visible |
| Input | Use reviewed source and scoped credentials | Revision and credential ID are attributable |
| Execution | Set label, timeout and concurrency policy | Queue and node evidence match intent |
| Recovery | Preserve the last known working state | Rollback is rehearsed before promotion |
Prepare the lab
Commit pom.xml, Maven Wrapper where organizational policy permits it, application code and tests. Decide whether Jenkins-managed tools, a pinned container or an approved agent image owns the JDK and Maven versions. Never accept whichever java or mvn happens to be first in PATH.
java -version
mvn --version
git status --short
./mvnw --version
./mvnw -B -ntp help:effective-pom > effective-pom.xml
./mvnw -B -ntp dependency:go-offlineImplement it step by step
- Use a repository mirror with TLS and scoped read credentials when public dependencies are proxied.
- Run compile and unit tests without skipping failures; publish Surefire XML even when a test fails.
- Run integration tests in an isolated stage with its required service dependencies.
- Package once, calculate SHA-256 and attach commit, tool and dependency evidence.
- Publish to an artifact repository only from a trusted branch or release tag.
- Promote the stored artifact to environments instead of rebuilding it.
Do not share one writable Maven local repository between concurrent untrusted builds. A cache may improve speed, but it is disposable acceleration rather than release evidence. Separate by workspace or controlled cache key, validate downloaded content through the repository manager and test a cold-cache build regularly.
pipeline {
agent { label 'linux && java21' }
options { timestamps(); timeout(time: 30, unit: 'MINUTES') }
stages {
stage('Verify tools') { steps { sh './mvnw --version; java -version' } }
stage('Test and package') { steps { sh './mvnw -B -ntp clean verify' } }
stage('Record artifact') { steps { sh 'sha256sum target/*.jar | tee target/SHA256SUMS' } }
}
post {
always { junit testResults: 'target/*-reports/*.xml', allowEmptyResults: false }
success { archiveArtifacts artifacts: 'target/*.jar,target/SHA256SUMS', fingerprint: true }
cleanup { deleteDir() }
}
}Maven phases have defined ordering. verify runs earlier compilation, testing and packaging phases before final verification; calling several lifecycle phases redundantly can repeat work. Distinguish a dependency-resolution failure, compiler error, unit-test failure, integration-test failure and repository publication rejection because their owners and recovery actions differ.
Credentials for reading dependencies are not the same as credentials for publishing releases. Pull-request builds need read-only dependency access and no deployment secret. Release publication should require a protected ref, approved version, immutable repository policy and a check that the version does not already exist. Snapshot and release repositories must not be interchangeable.
Verify the positive path
Compare the checked-out commit, JDK, Maven version, test totals, JAR manifest and SHA-256. Run once with an empty local cache to prove the repository declaration is complete, then rerun without source change and compare the artifact according to the project reproducibility policy.
git rev-parse HEAD
java -version
./mvnw --version
./mvnw -B -ntp clean verify
find target -maxdepth 2 -type f -printf '%s %p\n' | sort
sha256sum target/*.jar
unzip -p target/*.jar META-INF/MANIFEST.MF | sed -n '1,80p'| Check | Expected evidence | Reject when |
|---|---|---|
| Configuration | Matches reviewed source | UI drift or unresolved placeholder remains |
| Execution | Runs on the intended isolated node | Controller or wrong trust zone executes code |
| Evidence | Revision, result and outputs are retained | Green status has no attributable output |
| Recovery | Known state can be restored and verified | Recovery depends on an improvised manual edit |
Separate a failed test from a missing report
Change one assertion so Surefire exits non-zero. Jenkins must fail the build and still publish the failing test. Then misconfigure the report path in a separate lab run: the build should also fail because missing evidence is not accepted as zero tests. Restore both changes through Git.
sed -i 's/expectedValue/wrongValue/' src/test/java/example/AppTest.java
git diff -- src/test
./mvnw -B -ntp test; test $? -ne 0
find target -path '*reports*' -type f -maxdepth 4Troubleshoot by failed layer
| Symptom | Inspect | Correction |
|---|---|---|
| Queued or unavailable | Label, executor, node and network | Repair the failed scheduling or transport layer |
| Configuration rejected | Controller log, syntax and plugin ownership | Correct source; do not bypass validation |
| Job fails unexpectedly | First causal console error and agent logs | Fix one layer and rerun the smallest scope |
| Second run differs | Mutable dependency, workspace or UI drift | Pin inputs and remove hidden retained state |
Unsafe shortcuts
- Unsafe: granting administrator access to avoid designing a narrow permission removes accountability.
- Unsafe: binding protected secrets around untrusted repository code permits exfiltration despite masking.
- Unsafe: changing controller state without a verified backup and rollback turns a small error into an outage.
Operate, recover and retain evidence
Own the configuration, plugin and credential dependencies explicitly. Record the controller version, Git revision, immutable tool or artifact identity, initiator, approver and acceptance result. Rehearse the failure path on a disposable controller before adopting it as production procedure.
sudo systemctl stop jenkins
sudo rsync -aHAX --numeric-ids /backup/jenkins-known-good/ /var/lib/jenkins/
sudo restorecon -RF /var/lib/jenkins 2>/dev/null || true
sudo systemctl start jenkins
sudo journalctl -u jenkins -n 150 --no-pagerWorked use cases
| Situation | Design choice | Acceptance |
|---|---|---|
| Lab rollout | Apply to one disposable controller or folder | Positive, negative and recovery results are retained |
| Team rollout | Promote the same reviewed revision | Permissions and behavior remain consistent |
| Production change | Use backup, change window and acceptance | Failure stays bounded and rollback is tested |
Knowledge checks
Independent lab
- Capture the starting version, configuration and plugin evidence.
- Implement the change on a disposable controller or folder.
- Run one successful case and one controlled failure.
- Restore the accepted state and prove service behavior, not only process state.
- Repeat from a clean source checkout without relying on remembered UI actions.