gradle-semantic-release-plugin

August 13, 2026 · View on GitHub

⚠️ This project is discontinued

Do not use this plugin for new projects. It does not work with Gradle 9.

Use semantic-release itself, driving Gradle through @semantic-release/exec. This README is now a migration guide. See Why for the reasoning and Migration reference for a mapping of every configuration option this plugin offered.

Why this project is discontinued

This plugin was written in 2015, when there was no practical way to run the JavaScript semantic-release alongside a JVM build. That is no longer true, and the foundation this plugin was built on has gone away:

  • The entire version inference — roughly half the codebase — sits on top of org.ajoberstar:gradle-git 1.7.2, which has been dead since around 2017. Its successor, reckon, uses a fundamentally different model, so there is no migration path — only a rewrite.
  • The grgit Gradle plugin is explicitly not compatible with Gradle's configuration cache, and its author has stated that this is out of scope.
  • com.sun.jersey, Bintray, Artifactory, Coveralls and Travis CI — all dependencies of this build — are dead or discontinued.
  • ConfigureUtil, used by this plugin's extension, was removed in Gradle 9.

Rewriting all of that would produce something that semantic-release plus a four-line exec configuration already does, usually better. So this plugin is being retired instead.

One thing genuinely gets harder: the replacement requires Node.js in your CI. On GitHub Actions and most hosted runners it is preinstalled, so this is rarely a practical concern — but if your organisation does not allow npm dependencies in the build pipeline at all, this migration is not for you, and there is currently no maintained JVM-native equivalent.

Quick start

The setup below reproduces what this plugin did: infer the version from commit messages, tag the release, generate release notes, create a GitHub release with attached artifacts, and publish through Gradle.

1. Pin a placeholder version in gradle.properties

version=0.0.0-SNAPSHOT

Remove any version = ... assignment from build.gradle / build.gradle.kts. This placeholder is never updated in the repository — the real version is passed in at release time. This mirrors what semantic-release does in its own repository, where package.json permanently reads "version": "0.0.0-development".

2. Add .releaserc.json

{
  "branches": [
    "master",
    "+([0-9])?(.{+([0-9]),x}).x"
  ],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    [
      "@semantic-release/exec",
      {
        "prepareCmd": "./gradlew -Pversion=${nextRelease.version} build",
        "publishCmd": "./gradlew -Pversion=${nextRelease.version} publish"
      }
    ],
    [
      "@semantic-release/github",
      {
        "assets": [
          { "path": "build/libs/*.jar" }
        ]
      }
    ]
  ]
}

Replace publish with whatever your build actually uses — publishPlugins, publishToSonatype closeAndReleaseSonatypeStagingRepository, artifactoryPublish, and so on.

3. Add a GitHub Actions workflow

name: Release
on:
  push:
    branches: [master]

permissions:
  contents: write
  issues: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0          # semantic-release needs the full history and all tags
          persist-credentials: false

      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17

      - uses: gradle/actions/setup-gradle@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 24        # semantic-release 25 requires ^22.14.0 || >=24.10.0

      - run: npx semantic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Add whatever publishing credentials your Gradle build needs as further env entries — for the Gradle Plugin Portal, for instance, GRADLE_PUBLISH_KEY and GRADLE_PUBLISH_SECRET, wired through in the publishCmd.

That is the whole setup. Note the direction: Node starts, Node calls Gradle. Your Gradle build knows nothing about semantic-release, and local development is unaffected — developers keep running ./gradlew directly.

Migration reference

gradle-semantic-release-pluginReplacement
semanticRelease.changeLog.changeScope@semantic-release/commit-analyzerreleaseRules
semanticRelease.changeLog.changeLog@semantic-release/release-notes-generatorpreset / writerOpts
semanticRelease.releaseBranchestop-level branches
semanticRelease.branchNamesno equivalent needed — see SNAPSHOT versions
semanticRelease.repo.ghTokenGITHUB_TOKEN / GH_TOKEN environment variable
semanticRelease.repo.releaseAsset@semantic-release/githubassets
semanticRelease.repo.useGhEnterprise@semantic-release/githubgithubUrl
release.versionStrategy (rc branches etc.)branches entries with prerelease: true
release task, dependsOn build, finalizedBy publishprepareCmd / publishCmd

Release branches

The old default was master plus /(?:release[-\/])?\d+(?:\.\d+)?\.x/. semantic-release ships an equivalent maintenance-branch pattern out of the box:

{
  "branches": [
    "master",
    "stable",
    "+([0-9])?(.{+([0-9]),x}).x"
  ]
}

+([0-9])?(.{+([0-9]),x}).x matches 1.x, 1.0.x, 2.x and so on — the same guarantee against accidental major or minor bumps that the old branch patterns gave you.

Custom change scopes

The old changeScope closure returned MAJOR, MINOR, PATCH or null per commit. The declarative equivalent:

[
  "@semantic-release/commit-analyzer",
  {
    "preset": "conventionalcommits",
    "releaseRules": [
      { "type": "docs", "scope": "README", "release": "patch" },
      { "type": "refactor", "release": "patch" },
      { "scope": "no-release", "release": false }
    ]
  }
]

Defaults, if you specify nothing: breaking changes → major, featminor, fix and perfpatch, everything else → no release. That is exactly what this plugin did by default.

Custom changelog

The old changeLog closure built the release notes string by hand. Use @semantic-release/release-notes-generator with a preset, or writerOpts for full control:

[
  "@semantic-release/release-notes-generator",
  {
    "preset": "conventionalcommits",
    "presetConfig": {
      "types": [
        { "type": "feat", "section": "Features" },
        { "type": "fix", "section": "Bug Fixes" },
        { "type": "perf", "section": "Performance" },
        { "type": "chore", "hidden": true }
      ]
    }
  }
]

If you also want a committed CHANGELOG.md, add @semantic-release/changelog together with @semantic-release/git.

Release assets

[
  "@semantic-release/github",
  {
    "assets": [
      { "path": "build/libs/*.jar" },
      {
        "path": "build/libs/*-sources.jar",
        "name": "the-sources.jar",
        "label": "The sources jar"
      }
    ]
  }
]

path is a glob rather than a task reference, so make sure prepareCmd builds the artifacts before this step runs. name and label support templating with ${nextRelease.version} and friends.

GitHub Enterprise

[
  "@semantic-release/github",
  {
    "githubUrl": "https://github.enterprise",
    "githubApiPathPrefix": "/api/v3"
  }
]

Or via the GH_URL and GH_PREFIX environment variables.

Release candidates and other pre-releases

This used to require composing VersionStrategy objects with copyWith, StrategyUtil.all and Strategies.PreRelease.COUNT_INCREMENTED. It is now one line:

{
  "branches": [
    "master",
    { "name": "rc", "prerelease": true }
  ]
}

Commits on rc produce 1.2.0-rc.1, 1.2.0-rc.2, and so on.

SNAPSHOT versions on feature branches

This has no direct equivalent, by design. The old plugin inferred a 1.2.3-branchname-SNAPSHOT version for every build on a non-release branch; the branchNames extension existed only to tidy up those branch names.

semantic-release does not model unreleased builds at all — the version is treated as a property of the published artifact, not of the source tree. Local and feature-branch builds simply keep the 0.0.0-SNAPSHOT placeholder.

In practice the main thing this affects is publishToMavenLocal, and a fixed coordinate is arguably better there: a downstream project declares 0.0.0-SNAPSHOT once and always resolves your latest local build, instead of chasing a version that changes every commit.

If you genuinely need a descriptive version for local builds, derive it on the Gradle side with a ValueSource wrapping git describe --tags --abbrev=0. That stays configuration-cache safe, works in the IDE, and does not require Node. Do not wrap local builds in an npm script for this — IDEs invoke Gradle directly and would silently bypass the wrapper, giving you different versions on the command line and in the IDE.

FAQ

Can I preview which version would be published?

npx semantic-release --dry-run. This is better than what the old plugin offered, which was reading the version out of the build log.

Can I run a release from my own machine?

You can, but you probably should not, for the same reasons as before: releasing from an independent machine that has run the tests is the point of the workflow, and it means no long-lived tokens on developer laptops.

Does the version get committed back to the repository?

Not with the setup above, and that is deliberate. The git tag remains the single source of truth, and no chore commit is created per release. semantic-release itself recommends against committing the version back, since it adds meaningful complexity to the release process. If you want it anyway, add @semantic-release/git and have it commit gradle.properties.

What about the npm package gradle-semantic-release-plugin?

Unrelated to this project despite the identical name — it is a semantic-release plugin by Kengo TODA that also drives Gradle. It works, and it auto-detects your publish task by parsing the output of gradle tasks. That detection is its only advantage over @semantic-release/exec, and it costs two extra full Gradle configuration phases per release. If you already know your publish task, prefer exec.

Note that the scoped @gradle/semantic-release-plugin on npm is an obsolete alias by the same author, last released in 2019. Do not use it.

Should I use reckon instead?

Only if you want Gradle-native version inference without Node. Be aware that reckon is in maintenance mode — its GitHub repository was archived in September 2025 — and that its built-in commit-message scope calculator understands major: / minor: / patch: prefixes rather than Conventional Commits, so you would need to supply your own ScopeCalculator.

License

Apache License, Version 2.0 http://www.apache.org/licenses/

2015 © Tobias Schulte, based on the ideas of the semantic-release plugin of Stephan Bönnemann and contributors