Automating Maven Releases with GitHub Actions: No More Manual Hassles! - Part 1


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:

  1. Checkout the code from git, line 6-7.
  2. Setup Java correctly for the entire build (in our case using Java 24, with maven caching), line 8-14.
  3. Build the application to make sure there are no build issues, lines 15-16.
  4. Check the version in the project and store it for later use, lines 17-23.
  5. 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 and release, 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 using git 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.


See also