Manually releasing a Maven package is a hassle—updating versions, tagging commits, building artifacts, and pushing them to a package repository. What if you could automate all of this with a simple Git push?
With GitHub Actions, you can! In this guide, we’ll walk through setting up a workflow that automatically builds, versions, creates releases in GitHub—no manual steps required.
By the end, you’ll have a fully automated release pipeline that:
✅ Uses GitHub Actions to trigger releases
✅ Handles versioning and tagging seamlessly
Let’s dive in and supercharge your Maven workflow! 🚀
Setting up the project
Before we jump into automating Maven releases, make sure you have the following in place:
A GitHub Repository with a Maven Project
You’ll need a GitHub repository containing a Maven project (pom.xml
file). If you don’t have one yet, you can create a simple Maven project using:
1mvn archetype:generate -DgroupId=com.github.myuser -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Then, initialize a Git repository and push it to GitHub:
1git init
2git add .
3git commit -m "Initial commit"
4git branch -M main
5git remote add origin git@github.com/YOUR_USERNAME/YOUR_REPO.git
6git push -u origin main
Creating the release workflow
The first thing we have to consider is if we want to trigger the release manually using GitHub actions, or if we want it to trigger based upon creating releases inside GitHub. For this post we will set it up in a more manual way.
The rationale behind this is as follows:
- We want commits that fixate the release version.
- There should be a git tag pointing to that commit.
- We want a commit that prepares for the next snapshot build.
Setup the skeleton workflow
The first step is to setup a file in .github/workflows/create-release.yml
that will contain our entire workflow.
This skeleton will allow you to manually trigger the workflow and accepts one single input, a version postfix like RC-1.
1name: Create Release
2on:
3 workflow_dispatch:
4 inputs:
5 postfix:
6 description: 'Any postfix to be appended to the release, e.g.: RC1'
7 default: ''
8jobs:
Validate that the current version is a snapshot
Since we don’t want to re-release a maven build we first need to add a safeguard that validates that the branch we are building on is actually on a snapshot version.
In this part of the workflow we want to do the following:
- Checkout the code from git, line 6-7.
- Setup Java correctly for the entire build (in our case using Java 24, with maven caching), line 8-14.
- Build the application to make sure there are no build issues, lines 15-16.
- Check the version in the project and store it for later use, lines 17-23.
- Do the actual check to see if the version ends on
-SNAPSHOT
and exit if it does not, lines 24-32.
To do that we need to add section to the jobs
:
1 validate:
2 runs-on: ubuntu-latest
3 outputs:
4 version: ${{ steps.determine_version.outputs.version }}
5 steps:
6 - name: Checkout code
7 uses: actions/checkout@v4
8 - name: Set up JDK
9 uses: actions/setup-java@v4
10 with:
11 distribution: 'corretto'
12 java-version: '24'
13 architecture: x64
14 cache: 'maven'
15 - name: Build software
16 run: mvn -B install --no-transfer-progress --file pom.xml
17 - name: Determine version
18 id: determine_version
19 run: |
20 current_version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
21 echo "Current version: ${current_version}"
22 echo "version=${current_version}" >> $GITHUB_OUTPUT
23 echo "CURRENT_VERSION=${current_version}" >> $GITHUB_ENV
24 - name: Check if snapshot
25 run: |
26 echo "Project version is: ${{ env.CURRENT_VERSION }}"
27 if [[ "${{ env.CURRENT_VERSION }}" != *"-SNAPSHOT"* ]]; then
28 echo "::error::Release version should contain -SNAPSHOT"
29 exit 1
30 else
31 echo "Release version is correct (contains -SNAPSHOT)"
32 fi
Creating the release version
Now that we have validated we are safe to create a release version we need to setup a build step that will change the version of the project and push it correctly to git.
For this I added the following build step to the workflow:
1 release:
2 needs: validate
3 runs-on: ubuntu-latest
4 outputs:
5 tagged: ${{ steps.set_version.outputs.tag_version }}
6 steps:
7 - name: Checkout code
8 uses: actions/checkout@v4
9 - name: Set up JDK
10 uses: actions/setup-java@v4
11 with:
12 distribution: 'corretto'
13 java-version: '24'
14 architecture: x64
15 cache: 'maven'
16 - name: Configure Git
17 run: |
18 git config user.name "GitHub Actions"
19 git config user.email "user@users.noreply.github.com"
20 - name: Set release version
21 id: set_version
22 run: |
23 mvn versions:set versions:commit -DremoveSnapshot -q
24 RELEASE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
25 if [ -n "${{ github.event.inputs.postfix }}" ]; then
26 echo "Appending postfix: ${{ github.event.inputs.postfix }}"
27 mvn versions:set versions:commit -DnewVersion="${RELEASE_VERSION}-${{ github.event.inputs.postfix }}"
28 fi;
29
30 RELEASE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
31 echo "RELEASE_VERSION=${RELEASE_VERSION}" >> $GITHUB_ENV
32 echo "tag_version=${RELEASE_VERSION}" >> $GITHUB_OUTPUT
33 - name: Commit release
34 run: |
35 git add .
36 git commit -m "chore: prepare release ${{ env.RELEASE_VERSION }}"
37 git push
38 - name: Tagging stable release
39 run: |
40 git tag ${{ env.RELEASE_VERSION }}
41 git push origin ${{ env.RELEASE_VERSION }}
Lets explain this a bit as it is a lot of different steps that are taken. First we have the same steps as in most build parts, checking out the source code and setting up Java correctly (lines 7-15).
As we want to push to git we also need to setup the git account to use, this is done in lines 16-19, where we set the user to our git user name and e-mail (make sure to replace the user
with your own username).
The next step is to set the version of the Maven project, lines 20-32. To do this I use the maven-versions-plugin
bundled with Maven, instructing it to remove the -SNAPSHOT
from the version. Since we support creating releases containing postfixes we want to handle that correctly. In this build we are assuming that a postfix is used to create release candidates or beta releases. I append this postfix to the version using the same Maven plugin. The last part is to store the created version in both the GitHub environment using $GITHUB_ENV
as well as a output variable using$GITHUB_OUTPUT
.
The last two steps in this build stop commit the new version to git and push it. As well as creating a git tag with the version number as name.
Creating the GitHub release
After creating the tag and stable version of our project, we automate the creation of a GitHub Release using the softprops/action-gh-release
action. This ensures that every tagged release is documented in the repository’s Releases section.
1 github-release:
2 needs: release
3 runs-on: ubuntu-latest
4 if: ${{ needs.release.outputs.tagged }}
- This job is called
github-release
. - It depends on (
needs: release
) the previous release job, meaning it won’t run unless that job completes successfully. - It runs on the Ubuntu latest environment.
- The if condition ensures this job only runs if a new tag was created in the previous release step (
needs.release.outputs.tagged
).
1 steps:
2 - name: Create github release
3 uses: softprops/action-gh-release@v1
4 with:
5 tag_name: ${{ needs.release.outputs.tagged }}
6 name: Release ${{ needs.release.outputs.tagged }}
7 draft: true
8 prerelease: false
9 generate_release_notes: true
10 env:
11 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- This step creates a new GitHub Release using the
softprops/action-gh-release
action. - This action automates the process of publishing a release in the GitHub Releases section of the repository.
Were we setup the release using the following configuration: tag_name
: The release is associated with the tag generated in the previous release step (needs.release.outputs.tagged
).name
: The release is given a name like “Release v1.0.0”, based on the same tag.draft: true
: The release is initially set as a draft, meaning it won’t be published immediately. You can review it before making it public.prerelease: false
: Ensures the release is marked as a stable release, not a pre-release.generate_release_notes: true
: Automatically generates release notes based on commit messages and changes.
Setting the next snapshot
Once a release is completed, the next logical step is to prepare for future development by incrementing the version. This is where the “prepare-next” job comes in—it automatically updates the version in the pom.xml
file to the next snapshot version, commits the change, and pushes it back to the repository.
In this section, we’ll break down how this workflow ensures that every release is seamlessly followed by a new snapshot version, keeping the project ready for further development.
This job runs after validation and release are completed, ensuring that:
✅ The latest code is pulled.
✅ The version is updated to the next snapshot.
✅ Changes are committed and pushed back to the repository.
Let’s go step by step:
Defining the job
1prepare-next:
2 needs:
3 - validate
4 - release
5 runs-on: ubuntu-latest
6 steps:
7 - name: Checkout code
8 uses: actions/checkout@v4
9 - name: Set up JDK
10 uses: actions/setup-java@v4
11 with:
12 distribution: 'corretto'
13 java-version: '24'
14 architecture: x64
15 cache: 'maven'
- This job is named
prepare-next
. - It depends on both
validate
andrelease
, meaning it will only run after those jobs complete successfully. - It runs on the latest Ubuntu environment.
- Just like the jobs before this one checks out the code and prepares Java correctly.
Configuring Git for Automated Commits
1 - name: Configure Git
2 run: |
3 git config user.name "GitHub Actions"
4 git config user.email "user@users.noreply.github.com"
5 git pull
- Sets the Git identity to “GitHub Actions” with a noreply email.
- Runs
git pull
to fetch the latest changes before making modifications, this is needed since we committed in previous jobs in the workflow.
Updating the Version in pom.xml
1 - name: Prepare next build phase
2 run: |
3 if [ -n "${{ github.event.inputs.postfix }}" ]; then
4 mvn versions:set versions:commit -DnewVersion="${{ needs.validate.outputs.version }}" -q
5 else
6 mvn versions:set versions:commit -DnextSnapshot -q
7 fi;
8 NEXT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
9 echo "NEXT_VERSION=${NEXT_VERSION}" >> $GITHUB_ENV
- Automatically updates the project version to the next snapshot version.
- If a
postfix
input exists (e.g., a special versioning rule is applied), it sets the new version explicitly using the validated version. - Otherwise, it uses
-DnextSnapshot
to increment to the next logical snapshot version (e.g.,1.0.0 → 1.0.1-SNAPSHOT
). - Stores the new version in
GITHUB_ENV
, making it available for the next steps.
Committing and Pushing the Updated Version
1- name: Pushing committed snapshot
2 run: |
3 git add .
4 git commit -m "chore: prepare snapshot version ${{ env.NEXT_VERSION }}"
5 git push
- Stages the modified
pom.xml
file usinggit add .
. - Commits the change with a message indicating the new snapshot version.
- Pushes the commit back to the repository, ensuring the next development cycle starts with an updated snapshot version.
In conclusion
Manually managing Maven releases can be tedious, error-prone, and time-consuming. By leveraging GitHub Actions and GitHub Packages, we’ve built a fully automated release pipeline that:
✅ Builds and validates a Spring Boot Maven project
✅ Publishes artifacts to GitHub Packages automatically
✅ Creates GitHub releases for better version tracking
✅ Prepares the next snapshot version to keep development moving
With this setup, you no longer need to worry about manually tagging releases, updating versions, or handling deployments. Every commit and tag triggers a smooth, automated process—saving time, reducing errors, and ensuring a seamless development workflow.
In a follow up article we will continue on this setup to publish the project we created a release for to GitHub packages.