I was recently wasting time and energy to get the CI pipelines for my two main GitHub repositories working with Travis CI. Even though the documentation provides examples for Maven-based Java projects, it took me still some time to find the correct setup. While working on these pipelines, I remembered that GitHub now also provides its own CI/CD solution: GitHub Actions. As I wasn't quite satisfied with Travis CI, I gave it a try and got everything up- and running in less than one hour. With this blog post, I'll demonstrate how to use GitHub Actions for automating Maven workflows for Java projects.
Introduction to GitHub Actions
GitHub markets its GitHub Actions product as the following:
GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub. Make code reviews, branch management, and issue triaging work the way you want.
We can enter this feature on every (even private) GitHub repository with the Actions tab:
Within this tab, we get an overview of your latest builds and their logs like our might know it from Jenkins, Travis CI, Circle CI, etc. :
We configure your different pipeline steps as code and include them in your repository. The pipeline YAML definitions are then placed in the .github/workflows
folder.
Among other things, GitHub actions offers the following features:
- hosted runners for every OS (Windows, macOS, Linux)
- matrix builds to, e.g., test your library for different OS and programming language versions
- access to Docker to, e.g., use Testcontainers or a docker-compose.yml file for integration tests
- rich-feature marketplace next to pre-defined Actions provided by GitHub
- free for public repositories and limited contingent (minutes per month) for private repositories
- great integration for events of your GitHub repository (e.g., pull request, issue creation, etc.)
Let's use a Maven-based Java project to demonstrate how to use GitHub Actions…
Sample Workflow for a Maven-Based Java Application
As an example, we'll use a typical Java 11 Spring Boot Maven project to demonstrate the use of GitHub Actions. The project uses Testcontainers to access a PostgreSQL database during integration tests. The deployment target is Kubernetes, and the application is packaged inside a Docker container. This should reflect 80% of the requirements for a standard CI/CD pipeline these days.
In short, we want to achieve the following with our CI/CD pipeline using GitHub Actions:
- use different Java versions to compile the project (useful for library developers)
- cache the content of
.m2/repository
(or any other folder) for accelerated builds - use Maven to build the project
- stash build artifacts between different jobs
- access secrets (to, e.g., login to a container registry )
- make use of Docker
The workflow uses three jobs: compile
, build
and deploy
. Don't reflect on the meaningfulness of the following setup. We intentionally create a bloated pipeline to showcase as many features of GitHub Actions as possible.
Let's start with the compile
job:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | name: Build sample Java Maven project on: [push, pull_request] jobs: compile: runs-on: ubuntu-20.04 strategy: matrix: java: [ 11, 12, 13 ] name: Java ${{ matrix.java }} compile steps: - name: Checkout Source Code uses: actions/checkout@v2 - name: Setup Java uses: actions/setup-java@v2 with: distribution: 'adopt' java-package: jdk java-version: ${{ matrix.java }} - name: Compile the Project working-directory: github-actions-java-maven run: mvn -B compile |
We configure the job to run on a hosted ubuntu-20.04 runner. GitHub let's use choose between Ubuntu, Windows, and Mac as GitHub-hosted runners. Those runners already come with a decent amount of binaries and tools installed (e.g. AWS CLI, Maven, etc.). For a complete list of installed software, see the documentation on supported software.
Next, we define a matrix strategy to run the same job multiple times (in parallel) with different Java versions. The source code for the repository is not checked out on the runner by default. We use the checkout@v2
action for this purpose.
The setup-java@v2
action is used to set up the specific Java version on the runner and as part of the last step, we're compiling the Java project with Maven. With working-directory
we define in which folder we want to execute the commands. This way we don't have to explicitly perform any cd
operation.
Next comes the build
job:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | name: Build sample Java Maven project on: [push, pull_request] jobs: compile: # compile job build: runs-on: ubuntu-20.04 needs: compile name: Build the Maven Project steps: - uses: actions/checkout@v2 - uses: actions/cache@v2 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Set up JDK 11 uses: actions/setup-java@v2 with: distribution: 'adopt' java-version: '11' java-package: jdk - name: Build and test project working-directory: github-actions-java-maven run: mvn -B verify - name: Upload Maven build artifact uses: actions/upload-artifact@v2 with: name: artifact.jar path: github-actions-java-maven/target/github-actions-java-maven.jar |
By default, GitHub executes all jobs in parallel unless we specify that the job depends on the outcome of a previous job. This way we can ensure a sequential order.
Each GitHub Actions job starts with a fresh GitHub runner and hence we have to perform the VCS checkout and Java setup again. As an alternative, we can (and should have) performed the Maven build as part of the previous job.
Similar to the previous job, we're using Maven to now build the .jar
file. We'll now cache the contents of the .m2
folder to speed up subsequent builds as they don't have to download our dependencies over and over.
After we've successfully built our project, we want to share the build artifact with an upcoming job. As the jobs don't share the same filesystem we have to upload it. Once we've uploaded an artifact (this might also be a screenshot from a failing web test), another job can download it. Furthermore, we can also manually download any build artifacts ourselves:
And finally, we're (artificially) deploying the project:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | name: Build sample Java Maven project on: [push, pull_request] jobs: # existing jobs .. deploy: runs-on: ubuntu-20.04 needs: build name: Build Docker Container and Deploy to Kubernetes steps: - uses: actions/checkout@v2 - name: Download Maven build artifact uses: actions/download-artifact@v2 with: name: artifact.jar path: github-actions-java-maven/target - name: Build Docker container working-directory: github-actions-java-maven run: | docker build -t de.rieckpil.blog/github-actions-sample . - name: Access secrets env: SUPER_SECRET: ${{ secrets.SUPER_SECRET }} run: echo "Content of secret - $SUPER_SECRET" - name: Push Docker images run: echo "Pushing Docker image to Container Registry (e.g. ECR)" - name: Deploy application run: echo "Deploying application (e.g. Kubernetes)" |
We first download the Maven build artifact as we need it to build our Docker image.
Right after building the Docker image, we could now log in to our private Docker Registry to push our image. As this usually requires access to secrets (username and password) we demonstrate how to map secrets to environment variables. We can securely store those secrets as part of our GitHub repository (Settings -> Secrets).
What's left is to deploy the new Docker Image to our target environment (e.g. Kubeternes).
Blueprints for Real-World Workflow With GitHub Actions
Over the past month, I've migrated most of my GitHub projects to GitHub Actions and never looked back.
For further inspiration on how to use GitHub Actions, take a look at the following examples:
- Building and testing all blog post code examples as part of my blog-tutorials repository (Gradle, Maven, multiple Java versions, caching)
- Building and testing the source code for the Getting Started With Eclipse MicroProfile Course (Integration tests with MicroShed Testing, Docker, multiple projects, Open Liberty)
- For Stratospheric (a book about Spring Boot and AWS that I'm co-authoring), we're using GitHub actions for the entire CI/CD pipeline. We're building the Spring Boot backend, push the Docker image to ECR and create/sync our entire AWS infrastructure with the AWS CDK. Take a look at the various workflows for some inspiration and get a copy of this book if you want detailed information about this setup.
- If you're maintaining a Java library that you're publishing to Maven Central, consider this GitHub Actions workflow blueprint for building and deploying libraries.
- Aggregating Selenide screenshots to download in case of test failures for the Spring Boot Applications Masterclass
- Checking for rotten links in markdown files (must-have when writing eBooks)
To summarize, I can highly recommend GitHub Actions for Maven-based Java projects. The configuration is simple and you are ready in minutes.
Whether you are building a Java library or an application in a private repository, GitHub Actions allows you to easily set up CI/CD. Give it a try!
The Spring Boot application and the workflow definition is available on GitHub.
Have fun using GitHub Actions for your Java Maven projects,
Phil