A minimalistic Spring Boot demo application that demonstrates automated semantic versioning using semantic-release with a Maven-based project.
This project showcases how to integrate semantic-release with a Java Spring Boot application to automate version management and release processes. It provides a simple REST API service running on port 8081.
This project uses semantic-release to automate version management and package publishing. Semantic-release determines the next version number, generates release notes, and publishes the package automatically by analyzing commit messages.
- Commit messages in this project follow the Conventional Commits specification
- Based on these commit messages, semantic-release automatically determines the next version number using semantic versioning rules:
fix:commits trigger a PATCH release (1.0.0 → 1.0.1)feat:commits trigger a MINOR release (1.0.0 → 1.1.0)feat!:orfix!:commits (or commits with BREAKING CHANGE in the footer) trigger a MAJOR release (1.0.0 → 2.0.0)
When a release is triggered:
- semantic-release analyzes commits since the last release to determine the new version number
- The
release.shscript is executed with the new version number - Maven builds the JAR with the updated version
- The JAR artifact is copied to the
releasedirectory - The artifact is deployed to GitHub Packages
- A GitHub Release is created with the JAR attached as an asset
The semantic-release configuration is defined in .releaserc and includes:
- Branch configuration (releases from
main,betaanddevelopbranches) - Plugin configuration for analyzing commits, generating release notes, and publishing to GitHub
- The execution of
release.shto build and package the Java application
Each release produces the following artifacts:
-
JAR file:
service-b-x.y.z.jarin thereleasedirectory- This contains the compiled Spring Boot application
- Also uploaded as an asset to the GitHub Release
-
Maven package in GitHub Packages:
- The JAR is also deployed to GitHub Packages for use as a dependency in other projects
- Repository URL:
https://maven.pkg.github.com/emboss/service-b
This project uses Husky to enforce commit message conventions.
Husky enables Git hooks within the project to enforce standards and run scripts at specific points in the Git workflow.
The project includes a commit-msg hook that:
- Validates that commit messages follow the Conventional Commits format
- Ensures all commits can be properly analyzed by semantic-release
- Enforces the pattern:
type(scope): messagewhere type is one of:- feat, fix, chore, docs, test, style, refactor, perf, build, ci, revert, release
- Rejects commits that don't follow this pattern with a helpful error message
This ensures all commits are machine-readable for semantic-release to properly determine version numbers.
You can access the released artifacts in several ways:
-
Local Release Directory:
- After running
npm run release:local, check the localreleasefolder
- After running
-
GitHub Releases:
- Visit the GitHub repository's Releases page to download JARs for specific versions
- Each release includes automatically generated release notes based on commit messages
-
GitHub Packages:
- Add the repository to your Maven
settings.xmlto use this package as a dependency:
<repositories> <repository> <id>github</id> <url>https://maven.pkg.github.com/emboss/service-b</url> </repository> </repositories>
- Add the repository to your Maven
- JDK 21
- Node.js and npm (for semantic-release)
- Git
./mvnw clean packageTo manually trigger a release locally:
npm run release:localIn CI environments, releases are typically triggered automatically after pushing or merging to the release branches.
See the .github/workflows/release.yml for details on the CI/CD pipeline.
The intended workflow for this project is to adhere to the following Git branching model. We have three long-lived branches that are used to manage the release process and ensure a smooth development cycle:
- main: Stable releases, always deployable
- develop: Ongoing development, features in progress. This is where developers merge their feature branches when deemed ready.
- beta: Prepare for production release, merged into
mainwhen ready. After calling "feature freeze", the currentdevelopbranch is merged intobetafor final testing. When ready,betais merged intomainfor release. Finally,betais merged back intodevelopto keep it up to date, i.e. to not lose any changes/fixes made during the beta phase.
On top of these long-lived branches, we also use short-lived branches for specific purposes:
- feature branches: New features or fixes, merged into
developwhen complete - hotfix branches: Critical fixes directly applied to
mainand merged back intodevelop
To avoid accidentally deleting the develop and beta branches, protect them in the GitHub repository settings.
Since developers will mostly interact with the develop branch, it is recommended to set it as the default branch in the repository settings.
Feature and hotfix branches should be created from develop or main as appropriate, and merged back into develop or main when complete.
They can be safely deleted after merging, as they are short-lived and do not affect the long-lived branches.
The Github Actions workflow is configured to automatically run unit tests in the Test job any time a branch is pushed to Github.
If testing was successful, the Release job will run semantic-release on the main, beta, and develop branches. This ensures that
no releases are made without passing tests, and that the versioning and artifact publishing is handled automatically. At the same time,
developers can continue to work on new features in feature branches, which are merged into develop when ready. Feature branches will not trigger
a release, as semantic-release only runs on the main, beta, and develop branches.
Suppose the current version on the main branch is 1.0.0.
- A developer creates a feature branch and makes a single commit with the message:
feat(api): add new endpoint - They open a pull request and merge the feature branch into
develop.
Effect:
- On
develop, semantic-release is configured for pre-releases (e.g.,1.1.0-SNAPSHOT.1). - When the commit is merged, semantic-release analyzes the
featlabel and determines a minor version bump. - The next pre-release version on
developbecomes 1.1.0-SNAPSHOT.1 (or increments if previous pre-releases exist). - Artifacts are published as pre-releases, not as stable releases.
- The snapshot version will be deployed to GitHub Packages, allowing developers to test the new feature immediately.
- When development is ready for testing and/or to prepare a release using a release candidate,
developis merged intobeta.
Effect:
- On
beta, semantic-release is configured for pre-releases (e.g.,1.1.0-beta.1). - semantic-release analyzes all new commits since the last
betarelease. - The version is bumped to 1.1.0-beta.1 (or increments if previous beta pre-releases exist).
- Artifacts are published as beta pre-releases, suitable for final testing before production.
- The beta release will be deployed to GitHub Packages, and can be deployed on staging/testing servers as needed.
- After successful testing,
betais merged intomainfor production release. Note that bugs found during testing may still be fixed onbeta. This should only happen after a "feature freeze" where no new features are added, and only critical fixes are allowed. In order to ensure that thedevelopbranch is not left behind, thebetabranch is merged back intodevelopafter the release, i.e the merge intomain.
Effect:
- On
main, semantic-release creates a stable release. - All new commits since the last
mainrelease are analyzed. - The version is bumped to 1.1.0 (from 1.0.0), reflecting the new feature.
- Artifacts are published as stable releases, and the JAR is attached to the GitHub Release and deployed to GitHub Packages.
-
develop:
Used for ongoing development. semantic-release publishes pre-release versions with theSNAPSHOTtag (e.g.,1.1.0-SNAPSHOT.1). This allows developers to test integration without affecting production or beta users. -
beta:
Used for release candidates and final testing. semantic-release publishes pre-releases with thebetatag (e.g.,1.1.0-beta.1). This is the staging area before production, allowing QA and stakeholders to validate the release. -
main:
Used for stable, production-ready releases. semantic-release publishes stable versions (e.g.,1.1.0). Only thoroughly tested and approved changes reach this branch.
Summary Table:
| Branch | Example Version | Release Type | Audience |
|---|---|---|---|
| develop | 1.1.0-SNAPSHOT.1 | Pre-release | Developers |
| beta | 1.1.0-beta.1 | Beta pre-release | Testers/QA |
| main | 1.1.0 | Stable release | Production users |
Why this matters:
This branching and release strategy ensures that:
- New features and fixes are tested in isolation (
develop). - Release candidates are validated before production (
beta). - Developers can safely work on new features without affecting production stability since they may continue to merge new features
into
developbut at the same time, thebetabranch is frozen for new features and only critical fixes are allowed. - Only stable, verified code is released to users (
main). - semantic-release automates versioning and artifact publishing at each stage, reducing manual errors and ensuring consistency. Ideally, developers should not have to worry about version numbers, as semantic-release handles it based on commit messages.
| Scenario | Branch Action | Commit Message Example | Resulting Version (per branch) |
|---|---|---|---|
| Minor bump | feature/add-search → develop |
feat(search): add search endpoint |
develop: 1.1.0-SNAPSHOT.1 |
develop → beta |
(merge PR, no new commit) | beta: 1.1.0-beta.1 |
|
beta → main |
(merge PR, no new commit) | main: 1.1.0 |
|
| Bugfix bump | feature/fix-login → develop |
fix(auth): correct login bug |
develop: 1.0.1-SNAPSHOT.1 |
develop → beta |
(merge PR, no new commit) | beta: 1.0.1-beta.1 |
|
beta → main |
(merge PR, no new commit) | main: 1.0.1 |
|
| Major bump | feature/remove-legacy-api → develop |
feat!: remove legacy API endpoints |
develop: 2.0.0-SNAPSHOT.1 |
develop → beta |
(merge PR, no new commit) | beta: 2.0.0-beta.1 |
|
beta → main |
(merge PR, no new commit) | main: 2.0.0 |
Legend:
- Commit messages follow Conventional Commits.
feat:triggers a minor bump,fix:triggers a patch,feat!:(orBREAKING CHANGE) triggers a major bump.- Version numbers increment as changes are promoted through
develop→beta→main.
If you need to yank specific versions or tags and/or reset the versioning state for semantic-release, here are your main options:
- Delete the Git tag locally and remotely:
git tag -d v1.2.3 git push origin :refs/tags/v1.2.3
- Delete the GitHub Release (if created) via the GitHub web UI or using the GitHub CLI:
gh release delete v1.2.3
- GitHub Packages does not support deleting individual versions for Maven packages via the UI for public repos, but you can:
- Delete the entire package (removes all versions).
- For private repos, you can delete individual versions via the UI.
semantic-release determines the next version based on:
- The latest Git tag on the release branch (e.g.,
v1.2.3) - The commit history since that tag
To "reset" the version:
- Delete or move the problematic tag(s) as above.
- Create a new tag at the desired commit:
git tag v1.2.2 <commit-sha> git push origin v1.2.2
- On the next run, semantic-release will use this tag as the base for version calculation.
semantic-release does not have a built-in "set next version" command, but you can:
- Use the
semantic-release/gitplugin to push a new tag. - Or, manually create a tag as above.
- Or, use the
--dry-runflag to preview what would happen.
If you want to force a specific version on the next release, you can:
- Use a commit message with a release-as footer, e.g.:
This tells semantic-release to release the specified version.
chore(release): prepare for 2.0.0 [release-as: 2.0.0]
- You can force-push to rewrite history, but this is disruptive and should be avoided unless absolutely necessary.
| Action | How-To |
|---|---|
| Yank Git tag | git tag -d vX.Y.Z && git push origin :refs/tags/vX.Y.Z |
| Delete GitHub Release | GitHub UI or gh release delete vX.Y.Z |
| Yank Maven package | Delete package/version in GitHub Packages UI (if possible) |
| Set next version | Use [release-as: X.Y.Z] in commit, or manually tag desired version |
| Hard reset | Rewrite history and force-push (use with caution) |
References: