Jenkins for Beginners - Spring Boot Development Guide
What is Jenkins?
Jenkins is an open-source automation server that enables Continuous Integration (CI) and Continuous Deployment (CD) for software development. It helps automate the building, testing, and deployment of Spring Boot applications.
Why Jenkins for Spring Boot?
- Automation: Automatically build and test your Spring Boot apps on code changes
- Integration: Works seamlessly with Maven/Gradle, Git, Docker
- Flexibility: Extensive plugin ecosystem
- Scalability: Can handle multiple projects and environments
- Free: Open-source with strong community support
Core Jenkins Concepts
Key Terminology
- Job/Project: A runnable task in Jenkins (e.g., build Spring Boot app)
- Build: Single execution of a job
- Workspace: Directory where Jenkins runs your job
- Node/Agent: Machine where Jenkins executes jobs
- Pipeline: Code-based job definition using Groovy
- Plugin: Extension that adds functionality to Jenkins
Jenkins Architecture
┌─────────────────┐
│ Jenkins Master │ ← Web UI, Job scheduling, Plugin management
├─────────────────┤
│ Jenkins Agents │ ← Execute jobs, can be on different machines
├─────────────────┤
│ Source Code │ ← Git repositories
├─────────────────┤
│ Artifacts │ ← JAR files, Docker images
└─────────────────┘
Jenkins Installation
Local Installation (Development)
Option 1: Download WAR File
# Download Jenkins WAR
wget https://get.jenkins.io/war-stable/latest/jenkins.war
# Run Jenkins
java -jar jenkins.war --httpPort=8080
# Access: http://localhost:8080
Option 2: Docker (Recommended for beginners)
# Pull Jenkins image
docker pull jenkins/jenkins:lts
# Run Jenkins container
docker run -d \
--name jenkins \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
jenkins/jenkins:lts
# Access: http://localhost:8080
Option 3: Docker Compose
# docker-compose.yml
version: '3.8'
services:
jenkins:
image: jenkins/jenkins:lts
container_name: jenkins
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
environment:
- JENKINS_OPTS="--httpPort=8080"
restart: unless-stopped
volumes:
jenkins_home:
Initial Setup Steps
- Access Jenkins: Navigate to
http://localhost:8080
- Unlock Jenkins: Use initial admin password from logs
- Install Plugins: Choose "Install suggested plugins"
- Create Admin User: Set up your admin account
- Configure Instance: Set Jenkins URL
Essential Plugins for Spring Boot Development
Must-Have Plugins
# Core plugins for Spring Boot
- Git Plugin # Git integration
- Maven Integration Plugin # Maven support
- Gradle Plugin # Gradle support
- JUnit Plugin # Test result visualization
- Jacoco Plugin # Code coverage
- SonarQube Scanner # Code quality
- Docker Plugin # Docker integration
- Blue Ocean # Modern UI
- Pipeline Plugin # Pipeline as Code
- Credentials Plugin # Secure credential storage
- SSH Agent Plugin # SSH key management
Installation via UI
- Manage Jenkins → Manage Plugins
- Available tab → Search for plugins
- Select plugins → Install without restart
Installation via Plugin Manager
// plugins.txt (for automated installation)
git:latest
maven-plugin:latest
gradle:latest
junit:latest
jacoco:latest
sonar:latest
docker-plugin:latest
blueocean:latest
workflow-aggregator:latest
Creating Your First Spring Boot Job
Freestyle Project (Beginner-Friendly)
Step 1: Create New Job
- New Item → Enter name:
springboot-hello-world
- Select Freestyle project → OK
Step 2: Configure Source Code Management
# Git Configuration
Repository URL: https://github.com/yourusername/spring-boot-demo.git
Credentials: Add your Git credentials
Branch: */main
Step 3: Build Triggers
# Options:
☑ GitHub hook trigger for GITScm polling # Webhook-based
☑ Poll SCM: H/5 * * * * # Every 5 minutes
☑ Build periodically: H 2 * * * # Daily at 2 AM
Step 4: Build Environment
☑ Delete workspace before build starts
☑ Add timestamps to the Console Output
Step 5: Build Steps
# Add Build Step → Invoke top-level Maven targets
Goals: clean compile test package
Advanced:
- Maven Version: (Default)
- Settings file: settings.xml (if needed)
- Global Settings: (Default)
Step 6: Post-Build Actions
# Add post-build action → Archive the artifacts
Files to archive: target/*.jar
# Add post-build action → Publish JUnit test result report
Test report XMLs: target/surefire-reports/*.xml
# Add post-build action → Record jacoco coverage report
Path to exec files: target/jacoco.exec
Class Dirs: target/classes
Source Dirs: src/main/java
Sample Spring Boot Application Structure
spring-boot-demo/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/demo/
│ │ │ └── DemoApplication.java
│ │ └── resources/
│ │ └── application.properties
│ └── test/
│ └── java/
│ └── com/example/demo/
│ └── DemoApplicationTests.java
└── Jenkinsfile (for Pipeline)
Jenkins Pipelines for Spring Boot
Declarative Pipeline (Recommended for Beginners)
Basic Jenkinsfile
pipeline {
agent any
tools {
maven 'Maven-3.9'
jdk 'JDK-17'
}
environment {
SPRING_PROFILES_ACTIVE = 'test'
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'https://github.com/yourusername/spring-boot-demo.git'
}
}
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
publishTestResults testResultsPattern: 'target/surefire-reports/*.xml'
publishCoverage adapters: [
jacocoAdapter('target/site/jacoco/jacoco.xml')
]
}
}
}
stage('Package') {
steps {
sh 'mvn package -DskipTests'
}
post {
success {
archiveArtifacts artifacts: 'target/*.jar',
fingerprint: true
}
}
}
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('SonarQube') {
sh 'mvn sonar:sonar'
}
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
echo 'Deploying to staging environment...'
sh 'java -jar target/*.jar --server.port=8081 &'
sh 'sleep 30' // Wait for app to start
sh 'curl -f http://localhost:8081/actuator/health || exit 1'
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
input message: 'Deploy to production?', ok: 'Deploy'
echo 'Deploying to production environment...'
// Add production deployment steps
}
}
}
post {
always {
cleanWs()
}
success {
emailext (
subject: "✅ Build Success: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: "Build succeeded: ${env.BUILD_URL}",
to: "developer@company.com"
)
}
failure {
emailext (
subject: "❌ Build Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: "Build failed: ${env.BUILD_URL}",
to: "developer@company.com"
)
}
}
}
Advanced Pipeline with Docker
pipeline {
agent any
environment {
DOCKER_IMAGE = "myapp"
DOCKER_TAG = "${BUILD_NUMBER}"
REGISTRY_URL = "your-registry.com"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build & Test') {
parallel {
stage('Maven Build') {
steps {
sh 'mvn clean package'
}
}
stage('Unit Tests') {
steps {
sh 'mvn test'
}
post {
always {
publishTestResults testResultsPattern: 'target/surefire-reports/*.xml'
}
}
}
stage('Integration Tests') {
steps {
sh 'mvn verify -P integration-tests'
}
}
}
}
stage('Code Quality') {
parallel {
stage('SonarQube') {
steps {
withSonarQubeEnv('SonarQube') {
sh 'mvn sonar:sonar'
}
}
}
stage('Security Scan') {
steps {
sh 'mvn org.owasp:dependency-check-maven:check'
}
}
}
}
stage('Docker Build') {
steps {
script {
def image = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
docker.withRegistry("https://${REGISTRY_URL}", 'docker-registry-credentials') {
image.push()
image.push('latest')
}
}
}
}
stage('Deploy') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
steps {
script {
def environment = env.BRANCH_NAME == 'main' ? 'production' : 'staging'
sh "docker run -d --name ${environment}-${BUILD_NUMBER} -p 808${BUILD_NUMBER % 10}:8080 ${DOCKER_IMAGE}:${DOCKER_TAG}"
// Health check
sh "sleep 30"
sh "curl -f http://localhost:808${BUILD_NUMBER % 10}/actuator/health"
}
}
}
}
post {
always {
// Clean up Docker containers
sh "docker stop \$(docker ps -aq) || true"
sh "docker rm \$(docker ps -aq) || true"
cleanWs()
}
}
}
Multi-Branch Pipeline Setup
Step 1: Create Multi-Branch Pipeline
- New Item → Multibranch Pipeline
- Branch Sources → Git
- Project Repository: Your Git URL
- Credentials: Your Git credentials
- Scan Multibranch Pipeline Triggers: Periodically
Step 2: Branch Strategy
// Jenkinsfile - Branch-specific deployment
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'mvn clean package'
}
}
stage('Deploy') {
parallel {
stage('Deploy to Dev') {
when { branch 'feature/*' }
steps {
echo "Deploying feature branch to dev environment"
}
}
stage('Deploy to Staging') {
when { branch 'develop' }
steps {
echo "Deploying to staging environment"
}
}
stage('Deploy to Production') {
when { branch 'main' }
steps {
input message: 'Deploy to production?'
echo "Deploying to production environment"
}
}
}
}
}
}
Jenkins Configuration for Spring Boot
Global Tool Configuration
- Manage Jenkins → Global Tool Configuration
Maven Configuration
Name: Maven-3.9
Install automatically: ✓
Version: 3.9.0
JDK Configuration
Name: JDK-17
Install automatically: ✓
Add Installer: Install from adoptium.net
Version: jdk-17.0.7+7
Git Configuration
Name: Default
Path to Git executable: git (or /usr/bin/git)
System Configuration
- Manage Jenkins → Configure System
Global Properties
Environment variables:
- JAVA_HOME: /opt/java/openjdk
- MAVEN_HOME: /opt/maven
- PATH: $MAVEN_HOME/bin:$JAVA_HOME/bin:$PATH
Spring Boot Specific Configurations
Maven Settings for Jenkins
<!-- settings.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
<profiles>
<profile>
<id>jenkins</id>
<properties>
<spring.profiles.active>jenkins</spring.profiles.active>
<skipTests>false</skipTests>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>jenkins</activeProfile>
</activeProfiles>
</settings>
Application Properties for Testing
# application-jenkins.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
# Disable security for testing
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
# Logging
logging.level.com.example=DEBUG
logging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
POM.xml Configuration for Jenkins
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring-boot.version>3.1.0</spring-boot.version>
<jacoco.version>0.8.8</jacoco.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- Surefire for unit tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<testFailureIgnore>false</testFailureIgnore>
<skipTests>${skipTests}</skipTests>
</configuration>
</plugin>
<!-- Failsafe for integration tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- JaCoCo for code coverage -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Testing Strategies in Jenkins
Unit Tests with JUnit
stage('Unit Tests') {
steps {
sh 'mvn clean test'
}
post {
always {
publishTestResults testResultsPattern: 'target/surefire-reports/*.xml'
publishCoverage adapters: [
jacocoAdapter('target/site/jacoco/jacoco.xml')
], sourceFileResolver: sourceFiles('STORE_LAST_BUILD')
}
}
}
Integration Tests
stage('Integration Tests') {
steps {
sh 'mvn verify -P integration-tests'
}
post {
always {
publishTestResults testResultsPattern: 'target/failsafe-reports/*.xml'
}
}
}
Database Testing
pipeline {
agent any
services {
postgres {
image 'postgres:13'
environment {
POSTGRES_PASSWORD = 'test'
POSTGRES_DB = 'testdb'
}
}
}
stages {
stage('Database Tests') {
environment {
SPRING_DATASOURCE_URL = 'jdbc:postgresql://postgres:5432/testdb'
SPRING_DATASOURCE_USERNAME = 'postgres'
SPRING_DATASOURCE_PASSWORD = 'test'
}
steps {
sh 'mvn test -P database-tests'
}
}
}
}
Deployment Strategies
Simple Deployment
stage('Deploy') {
steps {
sh '''
# Stop existing application
pkill -f "spring-boot-demo" || true
# Deploy new version
nohup java -jar target/*.jar --server.port=8080 > app.log 2>&1 &
# Health check
sleep 30
curl -f http://localhost:8080/actuator/health
'''
}
}
Docker Deployment
stage('Docker Deploy') {
steps {
script {
// Build image
def image = docker.build("myapp:${BUILD_NUMBER}")
// Stop existing container
sh 'docker stop myapp-container || true'
sh 'docker rm myapp-container || true'
// Run new container
sh """
docker run -d \
--name myapp-container \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=production \
myapp:${BUILD_NUMBER}
"""
// Health check
sh 'sleep 30'
sh 'curl -f http://localhost:8080/actuator/health'
}
}
}
Blue-Green Deployment
stage('Blue-Green Deploy') {
steps {
script {
def newPort = env.CURRENT_PORT == '8080' ? '8081' : '8080'
// Deploy to new port
sh """
docker run -d \
--name myapp-${newPort} \
-p ${newPort}:8080 \
myapp:${BUILD_NUMBER}
"""
// Health check
sh "sleep 30"
sh "curl -f http://localhost:${newPort}/actuator/health"
// Switch traffic (update load balancer)
sh "echo 'Switching traffic to port ${newPort}'"
// Stop old version
sh "docker stop myapp-${env.CURRENT_PORT} || true"
// Update current port
env.CURRENT_PORT = newPort
}
}
}
Monitoring and Notifications
Email Notifications
post {
always {
emailext (
subject: "Build ${currentBuild.result}: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: """
Build ${currentBuild.result}
Job: ${env.JOB_NAME}
Build: ${env.BUILD_NUMBER}
URL: ${env.BUILD_URL}
Changes:
${env.CHANGE_LOG}
""",
recipientProviders: [
[$class: 'DevelopersRecipientProvider'],
[$class: 'RequesterRecipientProvider']
]
)
}
}
Slack Integration
post {
success {
slackSend(
channel: '#deployments',
color: 'good',
message: "✅ Deploy Success: ${env.JOB_NAME} - ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
)
}
failure {
slackSend(
channel: '#deployments',
color: 'danger',
message: "❌ Deploy Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
)
}
}
Troubleshooting Common Issues
Build Failures
Maven Issues
# Problem: Maven not found
Solution: Configure Maven in Global Tool Configuration
# Problem: Java version mismatch
Solution: Set JAVA_HOME correctly in pipeline:
environment {
JAVA_HOME = tool('JDK-17')
PATH = "${JAVA_HOME}/bin:${env.PATH}"
}
# Problem: Dependencies not downloading
Solution: Clear Maven cache:
sh 'rm -rf ~/.m2/repository'
Test Failures
// Problem: Tests failing in Jenkins but passing locally
stage('Debug Tests') {
steps {
sh 'mvn test -X' // Debug mode
sh 'cat target/surefire-reports/*.txt' // View test output
}
}
Permission Issues
# Problem: Permission denied
Solution: Fix file permissions:
sh 'chmod +x mvnw'
Performance Issues
// Problem: Slow builds
// Solution: Use parallel execution
pipeline {
agent any
stages {
stage('Parallel Tasks') {
parallel {
stage('Unit Tests') {
steps { sh 'mvn test' }
}
stage('Static Analysis') {
steps { sh 'mvn checkstyle:check' }
}
stage('Security Scan') {
steps { sh 'mvn dependency-check:check' }
}
}
}
}
}
Best Practices for Spring Boot with Jenkins
1. Pipeline Structure
// Good: Clear, readable stages
pipeline {
agent any
stages {
stage('Prepare') { ... }
stage('Build') { ... }
stage('Test') { ... }
stage('Quality') { ... }
stage('Package') { ... }
stage('Deploy') { ... }
}
}
2. Environment Management
// Good: Environment-specific configurations
environment {
SPRING_PROFILES_ACTIVE = "${env.BRANCH_NAME == 'main' ? 'prod' : 'dev'}"
DATABASE_URL = credentials('database-url')
}
3. Error Handling
// Good: Proper error handling
stage('Deploy') {
steps {
script {
try {
sh 'deploy.sh'
} catch (Exception e) {
currentBuild.result = 'FAILURE'
sh 'rollback.sh'
throw e
}
}
}
}
4. Security
// Good: Use credentials plugin
environment {
DB_PASSWORD = credentials('database-password')
API_KEY = credentials('external-api-key')
}
5. Resource Cleanup
// Good: Always clean up
post {
always {
sh 'docker container prune -f'
sh 'docker image prune -f'
cleanWs()
}
}
Jenkins Security Best Practices
1. User Management
- Enable matrix-based security
- Create role-based access
- Use LDAP/Active Directory integration
- Regular password policy updates
2. Plugin Security
- Keep plugins updated
- Remove unused plugins
- Review plugin permissions
- Use security scanning plugins
3. Pipeline Security
// Secure credential usage
pipeline {
agent any
environment {
SECRET_KEY = credentials('secret-key-id')
}
stages {
stage('Build') {
steps {
withCredentials([string(credentialsId: 'api-key', variable: 'API_KEY')]) {
sh 'echo "Using API key: $API_KEY"'
}
}
}
}
}
This comprehensive guide provides everything a beginner needs to start using Jenkins effectively with Spring Boot applications, from basic setup to advanced CI/CD pipelines.