Java projects built with Maven have a long history with Jenkins — in fact, Jenkins (originally Hudson) grew up largely in the Java ecosystem, and Maven support has always been one of its strongest features. I’ve built and maintained Maven pipelines for everything from small internal tools to sprawling enterprise applications, and in this guide, I’ll cover the full process of building a Maven project in Jenkins, from installation to advanced pipeline configuration.
Why Jenkins and Maven Work So Well Together
Maven’s structured lifecycle (validate, compile, test, package, verify, install, deploy) maps naturally onto CI/CD stages. Jenkins even has a dedicated Maven Project job type, plus a Maven Integration Plugin that provides deep integration — automatically parsing test results and build artifacts without extra configuration.
Jenkins Architecture Context
For Maven builds, agents need Java (matching your project’s target JDK version) and Maven itself installed, or configured via Jenkins’ Global Tool Configuration, which can auto-install specific Maven versions on demand.
Step 1: Install Jenkins
sudo apt update
sudo apt install openjdk-17-jdk -y
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \
/usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc]" \
https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt update
sudo apt install jenkins -y
sudo systemctl start jenkins
Step 2: Install the Maven Integration Plugin
Go to Manage Jenkins > Plugins > Available, search for “Maven Integration Plugin,” and install it. This adds the “Maven Project” job type and enhances Pipeline support for Maven-specific reporting.
Step 3: Configure JDK and Maven in Global Tool Configuration
Navigate to Manage Jenkins > Tools:
- Under JDK installations, add a JDK (e.g., name it
jdk-17), either pointing to an existing installation path or letting Jenkins auto-install it. - Under Maven installations, add a Maven version (e.g., name it
maven-3.9), similarly either pointing to a local install or enabling auto-install from Apache’s servers.
This lets your Jenkinsfiles reference these tools by name rather than hardcoding paths.
Step 4: Connect Your Git Repository
Create a new job (Maven Project or Pipeline) and configure Source Code Management to pull from your Git repository, just as you would for any other project type.
Step 5A: Using the Maven Project Job Type (Simpler, UI-Based)
If you choose “Maven Project” as your job type instead of Freestyle or Pipeline:
- Under “Root POM,” specify
pom.xml(default). - Under “Goals and options,” enter something like:
clean install
- Jenkins automatically detects your project’s modules, parses
pom.xmldependency and artifact information, and archives generated artifacts without extra plugins needed.
This job type is convenient for simple, single-module Maven projects but has become less common as teams shift to Pipeline-based configuration.
Step 5B: Using a Jenkinsfile (Pipeline, Recommended)
Here’s a complete Declarative Pipeline example for a Maven project:
pipeline {
agent any
tools {
maven 'maven-3.9'
jdk 'jdk-17'
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/yourusername/your-maven-app.git'
}
}
stage('Compile') {
steps {
sh 'mvn clean compile'
}
}
stage('Unit Tests') {
steps {
sh 'mvn test'
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
stage('Package') {
steps {
sh 'mvn package -DskipTests'
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
stage('Static Analysis') {
steps {
sh 'mvn checkstyle:check'
}
}
stage('Deploy to Artifact Repository') {
steps {
sh 'mvn deploy -DskipTests'
}
}
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
success {
echo 'Maven build completed successfully!'
}
failure {
echo 'Build failed — check the console output.'
}
}
}
The tools block automatically adds the specified JDK and Maven versions to the PATH for this pipeline run, so you don’t need to manually manage installation paths.
Step 6: Configuring settings.xml for Private Repositories
If your project pulls dependencies from a private Nexus or Artifactory repository, you’ll need a custom settings.xml with credentials. Store it as a “Secret file” credential in Jenkins, then reference it:
stage('Package') {
steps {
withCredentials([file(credentialsId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) {
sh 'mvn -s $MAVEN_SETTINGS clean package'
}
}
}
Step 7: Multi-Module Maven Projects
For projects with multiple modules defined in a parent pom.xml, Maven and Jenkins handle this natively — running mvn clean install from the root builds all modules in the correct dependency order. Jenkins’ Maven Integration Plugin (when using the Maven Project job type) even creates separate result tracking per module automatically.
Step 8: Integrating with SonarQube for Code Quality
Many Java teams add a SonarQube analysis stage:
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('MySonarServer') {
sh 'mvn sonar:sonar'
}
}
}
Combined with a Quality Gate step, this can even fail the pipeline if code quality drops below a defined threshold:
stage('Quality Gate') {
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
Step 9: Containerizing and Deploying
Once your JAR or WAR file is built, a common next step is containerizing it:
stage('Docker Build') {
steps {
sh 'docker build -t yourdockerhub/maven-app:${BUILD_NUMBER} .'
sh 'docker push yourdockerhub/maven-app:${BUILD_NUMBER}'
}
}
From there, deployment to Kubernetes, a traditional application server (like Tomcat), or a cloud platform follows the same patterns used across other project types.
Real-World Workflow Example
- Developer pushes to a feature branch → Jenkins compiles and runs unit tests.
- PR merges to
main→ full pipeline runs: compile, test, package, static analysis, SonarQube scan. - If quality gate passes, the JAR is deployed to an internal Nexus repository.
- A separate deployment pipeline builds a Docker image from that artifact and deploys it to a Kubernetes cluster.
Parallelizing Multi-Module Builds
For large multi-module Maven projects, build time can become a bottleneck. Maven itself supports parallel builds using the -T flag, which Jenkins can pass straight through:
stage('Parallel Build') {
steps {
sh 'mvn -T 4 clean install'
}
}
This tells Maven to use up to 4 threads for building independent modules simultaneously, which can meaningfully cut down build times on multi-core agents.
Integrating with Nexus or Artifactory for Snapshot and Release Management
Most real-world Maven setups publish both SNAPSHOT (in-development) and RELEASE (tagged, stable) versions to an internal artifact repository. A typical release stage might look like:
stage('Release') {
when {
buildingTag()
}
steps {
sh 'mvn versions:set -DnewVersion=${TAG_NAME}'
sh 'mvn deploy -DskipTests'
}
}
This pattern ties your Maven release versioning directly to Git tags, keeping your artifact repository and version control history in sync.
Caching the Local Maven Repository for Faster Builds
Downloading the same dependencies repeatedly across builds wastes time and bandwidth. Mounting a persistent .m2 directory (via a Docker volume or a shared path on the agent) lets Maven reuse previously downloaded artifacts:
agent {
docker {
image 'maven:3.9-eclipse-temurin-17'
args '-v $HOME/.m2:/root/.m2'
}
}
Just be mindful of concurrent builds sharing the same .m2 cache directory, since simultaneous writes to the same repository path can occasionally cause corruption on heavily parallelized setups.
Troubleshooting Common Issues
- “mvn: command not found”: Confirm the
toolsblock correctly references a Maven installation name configured in Global Tool Configuration. - Dependency resolution failures: Check network access to your Maven repository (Maven Central or private Nexus/Artifactory) and verify
settings.xmlcredentials. - Tests reported as passed but build still fails: Check for compilation errors in test code itself, which can cause Surefire to fail before generating reports.
- Out of memory errors during build: Increase Maven’s heap size using
MAVEN_OPTS="-Xmx2g"as an environment variable in your pipeline.
Security Best Practices
- Never commit
settings.xmlwith plaintext credentials to your repository; use Jenkins Credentials instead. - Run
mvn dependency-check:check(OWASP Dependency-Check Plugin) to catch known vulnerabilities in your dependency tree. - Sign your artifacts (using GPG) if you’re publishing to public repositories like Maven Central.
- Regularly update the Maven Integration Plugin and JDK versions to receive security patches.
FAQs
Q: Should I use the Maven Project job type or a Pipeline with Jenkinsfile? Pipeline with a Jenkinsfile is generally recommended for anything beyond a very simple single-module project, since it gives you version-controlled, flexible build logic.
Q: Can Jenkins cache Maven’s local repository (.m2) between builds? Yes — mount a persistent volume or directory for .m2 across builds (careful with concurrent builds on shared agents) to significantly speed up dependency resolution.
Q: How do I handle multiple JDK versions for different projects? Configure multiple JDK installations in Global Tool Configuration, then reference the specific one needed in each project’s tools block.
Q: Does Jenkins support Gradle as well as Maven? Yes, via the Gradle Plugin, following a very similar pattern — configure a Gradle installation in Global Tool Configuration and reference it in your Jenkinsfile’s tools block.
Summary
Building a Maven project in Jenkins is a well-trodden path with excellent tooling support — from the dedicated Maven Integration Plugin to native JUnit and artifact handling. Whether you use the classic Maven Project job type or a modern Jenkinsfile-based Pipeline, Jenkins gives you a reliable way to compile, test, package, analyze, and deploy your Java applications.