Lesson 014 · Jenkins Learning Path

Build, Test and Package Java with Maven and Jenkins

· Published · 6 min read

Labelled Jenkins CI/CD path separating untrusted pull request validation from trusted immutable artifact approval deployment monitoring and rollback

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/null

Understand the operating boundary

DecisionImplementationEvidence
ScopeName the controller, folder, job, node and environmentThe selected boundary is visible
InputUse reviewed source and scoped credentialsRevision and credential ID are attributable
ExecutionSet label, timeout and concurrency policyQueue and node evidence match intent
RecoveryPreserve the last known working stateRollback 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-offline

Implement it step by step

  1. Use a repository mirror with TLS and scoped read credentials when public dependencies are proxied.
  2. Run compile and unit tests without skipping failures; publish Surefire XML even when a test fails.
  3. Run integration tests in an isolated stage with its required service dependencies.
  4. Package once, calculate SHA-256 and attach commit, tool and dependency evidence.
  5. Publish to an artifact repository only from a trusted branch or release tag.
  6. 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'
CheckExpected evidenceReject when
ConfigurationMatches reviewed sourceUI drift or unresolved placeholder remains
ExecutionRuns on the intended isolated nodeController or wrong trust zone executes code
EvidenceRevision, result and outputs are retainedGreen status has no attributable output
RecoveryKnown state can be restored and verifiedRecovery 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 4

Troubleshoot by failed layer

SymptomInspectCorrection
Queued or unavailableLabel, executor, node and networkRepair the failed scheduling or transport layer
Configuration rejectedController log, syntax and plugin ownershipCorrect source; do not bypass validation
Job fails unexpectedlyFirst causal console error and agent logsFix one layer and rerun the smallest scope
Second run differsMutable dependency, workspace or UI driftPin 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-pager

Worked use cases

SituationDesign choiceAcceptance
Lab rolloutApply to one disposable controller or folderPositive, negative and recovery results are retained
Team rolloutPromote the same reviewed revisionPermissions and behavior remain consistent
Production changeUse backup, change window and acceptanceFailure stays bounded and rollback is tested

Knowledge checks

What does Maven verify do?
Runs the lifecycle through package and verification steps.
Why use -B and -ntp?
Batch mode avoids interaction and no-transfer-progress keeps CI logs readable.
Why separate read and deploy credentials?
Untrusted validation does not need artifact publication authority.
Why keep controller configuration in source?
It provides review, attribution and repeatable recovery.
Why record the exact Jenkins version?
Core and plugin behavior depends on the running baseline.
Does a successful process prove service acceptance?
No; verify the user-facing or downstream result.
Why test one negative case?
It proves the control rejects an invalid or unauthorized path.
Why use a disposable rehearsal?
Controller changes can prevent the same interface from repairing itself.

Independent lab

  1. Capture the starting version, configuration and plugin evidence.
  2. Implement the change on a disposable controller or folder.
  3. Run one successful case and one controlled failure.
  4. Restore the accepted state and prove service behavior, not only process state.
  5. Repeat from a clean source checkout without relying on remembered UI actions.

Official references

Advertisement