A real Java pipeline, end to end

Jenkins CI/CD Course · lesson 11 of 15 · 6 min read

Build, test, gate on quality, publish an image and deploy - assembled one directive at a time.

Open this lesson in the learning hub

Key points

  • Run Maven with -B -ntp so the console is build output instead of thousands of download lines.
  • Gradle on an agent should pass --no-daemon, because a leftover daemon holds heap between builds.
  • Cache ~/.m2 or ~/.gradle by mounting a volume into the container, not by copying it around.
  • withSonarQubeEnv injects the server URL and token, and waitForQualityGate needs a webhook back from SonarQube.
  • Tag the image with BUILD_NUMBER and the short commit so a running container maps to one build.
  • Deploy is just another stage, but give it its own agent label and its own credential scope.

Example

pipeline {
  agent none
  options { timeout(time: 45, unit: 'MINUTES') }

  environment {
    IMAGE = "registry.example.com/orders-api:${BUILD_NUMBER}"
  }

  stages {
    stage('Build and test') {
      agent { docker { image 'maven:3.9-eclipse-temurin-17'
                       args '-v $HOME/.m2:/var/maven/.m2 -e MAVEN_CONFIG=/var/maven/.m2' } }
      steps {
        sh 'mvn -B -ntp -Duser.home=/var/maven -Dmaven.test.failure.ignore=true verify'
        // Analysis runs HERE, in the workspace that holds target/classes. On a fresh
        // agent sonar:sonar fails with "Please provide compiled classes of your project".
        withSonarQubeEnv('sonar') { sh 'mvn -B -ntp -Duser.home=/var/maven sonar:sonar' }
      }
      post {
        always {
          junit '**/target/surefire-reports/*.xml'
          stash name: 'jar', includes: 'target/*.jar'
        }
      }
    }

    stage('Quality gate') {
      // No agent needed: this only waits for SonarQube to call the webhook back,
      // so it holds a flyweight executor rather than a whole build agent.
      agent none
      steps {
        timeout(time: 10, unit: 'MINUTES') {
          waitForQualityGate abortPipeline: true
        }
      }
    }

    stage('Publish image') {
      agent { label 'docker-build' }
      steps {
        unstash 'jar'
        sh 'docker build -t $IMAGE .'
        withCredentials([usernamePassword(credentialsId: 'registry-creds',
                                          usernameVariable: 'U',
                                          passwordVariable: 'P')]) {
          sh 'set +x; echo "$P" | docker login registry.example.com -u "$U" --password-stdin'
        }
        sh 'docker push $IMAGE'
      }
    }

    stage('Deploy to production') {
      when { beforeInput true; branch 'main' }
      input { message 'Ship to production?'; ok 'Deploy' }
      agent { label 'deploy' }
      steps { sh './deploy.sh "$IMAGE"' }
    }
  }
}

Pin the toolchain, stash between agents, gate on quality and keep deploy on its own agent behind a when guard.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Jenkins CI/CD Course course, and every lesson in it is listed on the Jenkins CI/CD Course contents page.