Welcome to Chapter 18 of our comprehensive Java project guide! In this chapter, we’ll take a significant leap towards professional software development by implementing Continuous Integration/Continuous Deployment (CI/CD) for our “Basic To-Do List Application” using GitHub Actions. CI/CD is a set of practices that enable development teams to deliver code changes more frequently and reliably by automating the build, test, and deployment processes.
This step is crucial because it automates the repetitive tasks of building and testing our application every time a change is pushed to the repository. This ensures that new code integrations are immediately validated, catching bugs early, improving code quality, and reducing the risk of introducing regressions. By the end of this chapter, you will have a robust CI pipeline set up that automatically builds and tests your Java application on every code commit, providing immediate feedback on the health of your codebase.
Prerequisites: Before we begin, ensure you have:
- A GitHub account and a repository for your “Basic To-Do List Application”.
- The “Basic To-Do List Application” project (or any of your chosen Java projects) pushed to this GitHub repository. We will assume it’s a Maven-based project for this guide.
- Familiarity with Git and GitHub basics.
Expected Outcome: Upon completing this chapter, you will have a GitHub Actions workflow configured in your repository that automatically:
- Checks out your project code.
- Sets up the correct Java Development Kit (JDK) environment.
- Builds your Java application using Maven.
- Runs all unit and integration tests defined in your project.
- Reports the status of the build and tests directly in GitHub.
Planning & Design: CI Workflow
Our goal is to create a simple yet effective CI workflow. The core idea is to define a series of automated steps that run whenever specific events occur in our GitHub repository, such as pushing code to a branch or opening a pull request.
Workflow Architecture:
- Trigger Events: Push to the
mainbranch, or any pull request opened againstmain. - Jobs: A single job named
build-and-testthat runs on anubuntu-latestvirtual machine. - Steps within the Job:
- Checkout Code: Get the latest version of the repository.
- Setup Java: Configure the correct JDK version (Java 24, as of December 2025).
- Cache Maven Dependencies: Speed up subsequent runs by caching downloaded dependencies.
- Build with Maven: Compile the project and package it.
- Run Tests: Execute all defined unit and integration tests.
File Structure:
GitHub Actions workflows are defined in YAML files located in the .github/workflows/ directory at the root of your repository. We will create a file named java-ci.yml.
your-java-project/
├── .github/
│ └── workflows/
│ └── java-ci.yml <-- This is where our workflow will live
├── src/
├── pom.xml
└── ... (other project files)
Step-by-Step Implementation
Let’s begin by creating our CI workflow file.
a) Setup/Configuration
First, navigate to the root of your Java project in your local development environment.
Create the Workflow Directory: If it doesn’t already exist, create a directory named
.githubat the root of your project. Inside.github, create another directory namedworkflows.mkdir -p .github/workflowsCreate the Workflow File: Inside the newly created
.github/workflowsdirectory, create a new file namedjava-ci.yml.touch .github/workflows/java-ci.ymlAdd Basic Workflow Structure: Open
java-ci.ymland add the initial structure. This defines the workflow’s name and when it should run.File:
.github/workflows/java-ci.ymlname: Java CI/CD # Define when the workflow should run on: push: branches: [ main ] # Trigger on pushes to the 'main' branch pull_request: branches: [ main ] # Trigger on pull requests targeting 'main' # Define the jobs that make up this workflow jobs: build-and-test: name: Build and Test runs-on: ubuntu-latest # Specify the runner environment # Steps sequence steps: # Step 1: Checkout the repository code - name: Checkout Code uses: actions/checkout@v4 # Step 2: Set up Java Development Kit (JDK) - name: Set up JDK 24 uses: actions/setup-java@v4 with: java-version: '24' # Using Java 24, latest stable as of Dec 2025 distribution: 'temurin' # Recommended distribution for GitHub Actions cache: 'maven' # Cache Maven dependencies for faster builds # Step 3: Build the project with Maven and run tests - name: Build and Test with Maven run: | echo "Building project and running tests..." mvn -B clean install # The -B flag for batch mode, clean install to build and run tests echo "Build and tests completed."
b) Core Implementation
Let’s break down the java-ci.yml file step by step.
name: Java CI/CD: This is the name that will appear in the GitHub Actions tab for your workflow. It’s descriptive and helps identify the workflow.on:: This section defines the events that trigger the workflow.push: branches: [ main ]: The workflow will run whenever code is pushed to themainbranch.pull_request: branches: [ main ]: The workflow will also run whenever a pull request is opened or updated, targeting themainbranch. This is crucial for pre-merge validation.
jobs:: A workflow can have one or more jobs. We have a single job namedbuild-and-test.name: Build and Test: A human-readable name for the job.runs-on: ubuntu-latest: Specifies the type of virtual machine that the job will run on.ubuntu-latestis a common and robust choice for Java projects.
steps:: This is an ordered list of tasks that the job will execute.- name: Checkout Code:uses: actions/checkout@v4: This is a GitHub Action that checks out your repository code onto the runner.@v4specifies using version 4 of this action, which is the latest stable.
- name: Set up JDK 24:uses: actions/setup-java@v4: This action sets up a Java environment.with:: This block provides configuration options for thesetup-javaaction.java-version: '24': We specify Java 24, which is the latest stable version as of December 2025 according to Oracle’s CPU release notes (24.0.2 in July 2025).distribution: 'temurin': Temurin (formerly AdoptOpenJDK) is a popular and open-source distribution of the JDK, widely used in CI/CD environments.cache: 'maven': This is a critical optimization. It tells thesetup-javaaction to cache Maven dependencies. The first time the workflow runs, Maven downloads all required dependencies. Subsequent runs will retrieve these dependencies from the cache, significantly speeding up the build process.
- name: Build and Test with Maven:run: |: This executes a multi-line shell command.echo "Building project and running tests...": A simple log message.mvn -B clean install: This is the core command.mvn: Invokes the Maven build tool.-B: Batch mode. This prevents Maven from prompting for any input, which is essential for automated environments.clean: Cleans the target directory, removing any previously compiled classes.install: Compiles the source code, runs tests, and packages the compiled code into a JAR/WAR file, then installs it into the local Maven repository. If any tests fail, this command will exit with a non-zero status, causing the GitHub Actions job to fail.
echo "Build and tests completed.": Another log message.
c) Testing This Component
To test your new CI workflow:
Commit and Push: Save the
java-ci.ymlfile. Then, commit the changes to your Git repository and push them to GitHub.git add .github/workflows/java-ci.yml git commit -m "feat: Add GitHub Actions CI workflow for Java project" git push origin mainObserve GitHub Actions:
- Go to your GitHub repository in your web browser.
- Click on the “Actions” tab.
- You should see your “Java CI/CD” workflow listed, with a status indicating it’s running or has just completed.
- Click on the workflow run to see the details of each step. You can expand each step to view its logs, confirming that the code was checked out, Java was set up, and Maven built and tested your project.
Simulate a Failure (Optional but Recommended): To understand how failures are reported:
- Locally, introduce a failing test into your
Basic To-Do List Application(e.g., change an assertion in an existing test toassert false). - Commit this change and push it to your
mainbranch. - Observe the “Actions” tab again. This time, the workflow run should show a red ‘X’ indicating a failure. You can drill down into the logs to see which step failed (likely “Build and Test with Maven” due to the failing test).
- Remember to revert this failing test afterward and push again to restore a passing build.
- Locally, introduce a failing test into your
Production Considerations
While our basic CI workflow is functional, a production-ready setup requires additional considerations:
Error Handling & Notifications:
- GitHub Actions automatically marks a job as failed if any step exits with a non-zero status.
- You can configure repository settings or use GitHub integrations (like Slack, email) to receive notifications when a workflow fails, ensuring your team is immediately aware of build breakages.
- For more advanced error handling within the workflow, consider using
if: always()orif: failure()conditions for specific steps (e.g., to upload logs or artifacts even on failure).
Performance Optimization:
- Maven Dependency Caching: We’ve already included
cache: 'maven'in oursetup-javastep. This is crucial for performance. GitHub Actions automatically manages the cache, creating a new one if dependencies change. - Parallelization: For larger projects with many modules, you could split the
build-and-testjob into multiple jobs that run in parallel (e.g.,build-module-A,test-module-A,build-module-B,test-module-B). - Selective Testing: In some advanced scenarios, you might only run tests for modules affected by a change, though this adds complexity.
- Maven Dependency Caching: We’ve already included
Security Considerations:
Least Privilege: Ensure your workflow only has the minimum necessary permissions. By default,
actions/checkoutandactions/setup-javarun with theGITHUB_TOKENwhich has limited permissions, usually sufficient for CI.Secrets: If your build process requires sensitive information (e.g., API keys, database credentials for integration tests), use GitHub Secrets. Never hardcode sensitive data directly into your workflow file. You can access secrets using
secrets.MY_SECRET_NAME.- name: Run Integration Tests with Secret env: DB_PASSWORD: ${{ secrets.DB_PASSWORD }} # Accessing a GitHub Secret run: mvn verify -Pintegration-testsCode Scanning: Integrate GitHub’s CodeQL or other static analysis tools into your CI workflow to automatically scan for security vulnerabilities in your code.
Logging and Monitoring:
- The GitHub Actions UI provides detailed logs for each step, which is excellent for debugging.
- For long-term monitoring and analytics, you could integrate with external logging platforms (e.g., Splunk, ELK stack) by adding steps to push build logs to these services.
Code Review Checkpoint
At this point, you have successfully configured a basic CI workflow for your Java project.
Summary of what was built:
- A
.github/workflows/java-ci.ymlfile was created. - This file defines a workflow named “Java CI/CD”.
- The workflow is triggered on
pushtomainandpull_requesttargetingmain. - It consists of a
build-and-testjob running onubuntu-latest. - The job includes steps to checkout code, set up Java 24 (Temurin distribution) with Maven caching, and execute a
mvn clean installcommand to build and test the project.
Files created/modified:
./github/workflows/java-ci.yml(new file)
How it integrates with existing code:
This CI workflow integrates seamlessly with your existing Java project by using its pom.xml (or build.gradle if you were using Gradle) to define the build process and dependencies. It doesn’t modify your application code itself but rather automates the validation of that code.
Common Issues & Solutions
Issue: Workflow fails with “command not found: mvn” or similar.
- Cause: Maven (or Gradle) is not correctly installed or not in the system’s PATH on the runner.
- Solution: Ensure the
actions/setup-java@v4step includescache: 'maven'orcache: 'gradle', which helps set up the build tool. Also, verify that yourpom.xml(orbuild.gradle) is at the root of your repository or specify its path correctly in themvncommand. - Prevention: Always use the
setup-javaaction with thecacheparameter set to your build tool (mavenorgradle).
Issue: Workflow fails during dependency download (e.g., “Could not resolve dependencies”).
- Cause: Network issues, incorrect repository URLs in
pom.xml, or issues with the Maven Central repository. - Solution: Check your
pom.xmlfor any custom repository configurations that might be incorrect or unreachable. Sometimes, transient network issues occur; re-running the workflow might resolve it. If it persists, ensure your project’s dependencies are publicly available or configured correctly if they are private. - Debugging: Review the logs from the “Build and Test with Maven” step carefully for specific error messages related to dependency resolution.
- Cause: Network issues, incorrect repository URLs in
Issue: Workflow runs but tests are skipped or not found.
- Cause: Your Maven
pom.xmlmight not be configured to run tests (e.g.,maven-surefire-pluginormaven-failsafe-pluginare missing or misconfigured), or your test files are not following standard naming conventions (e.g.,*Test.java). - Solution: Verify your
pom.xmlincludes the necessary plugins for running unit and integration tests. Ensure your test classes are insrc/test/javaand follow the*Test.javaorTest*.javanaming patterns. - Prevention: Adhere to standard Maven project structure and plugin configurations for tests from the start.
- Cause: Your Maven
Testing & Verification
To ensure your CI setup is robust and fully functional:
Verify a Successful Build:
- Push a small, harmless change to your code (e.g., add a comment, fix a typo).
- Observe the GitHub Actions tab. The “Java CI/CD” workflow should trigger, run successfully, and show a green checkmark.
- Review the logs to confirm that all steps completed as expected, especially the Maven build and test execution.
Verify a Failed Build (Intentional Test Failure):
- Temporarily modify one of your existing unit tests to intentionally fail (e.g., change
assertEquals(expected, actual)toassertEquals(wrongExpected, actual)). - Commit and push this change.
- Observe the GitHub Actions tab. The workflow should trigger and fail with a red ‘X’.
- Examine the logs for the “Build and Test with Maven” step to see the test failure report.
- Crucially, revert this intentional failure and push again to ensure your
mainbranch always has a passing CI build.
- Temporarily modify one of your existing unit tests to intentionally fail (e.g., change
Verify Pull Request Trigger:
- Create a new branch from
main(e.g.,git checkout -b feature/test-ci). - Make a small change in this branch.
- Push the branch to GitHub (
git push origin feature/test-ci). - Open a Pull Request on GitHub from
feature/test-citomain. - Observe the “Checks” section of the Pull Request. Your “Java CI/CD” workflow should automatically trigger and run against the PR branch. This provides immediate feedback on whether the proposed changes break the build or tests before they are merged.
- Create a new branch from
Summary & Next Steps
Congratulations! You have successfully implemented a Continuous Integration pipeline for your Java project using GitHub Actions. This is a foundational step for any production-ready application. You now have an automated system that validates your code changes, providing rapid feedback and maintaining code quality.
What was accomplished:
- You learned the importance of CI/CD in modern software development.
- You configured a GitHub Actions workflow to automatically build and test your Java application.
- You understood how to use
actions/checkoutandactions/setup-javato prepare the build environment. - You implemented Maven commands within the workflow to compile and run tests.
- You explored production considerations like caching, security, and error handling.
This CI setup is a crucial prerequisite for Continuous Deployment. In the next chapter, Chapter 19: Deploying to a Cloud Platform (e.g., AWS Elastic Beanstalk), we will extend this pipeline to automatically deploy our application to a cloud environment whenever a successful build occurs on the main branch. This will complete our journey from code commit to a live, accessible application.